Files
HolodoriCalc/src/lib/engine/chart-navigation.ts
T
dal4segno 0ccf57d535
Publish GitHub Pages output / publish (push) Successful in 49s
feat: improve chart navigation and show original measure numbers
2026-10-04 23:05:57 +09:00

42 lines
1.9 KiB
TypeScript

/** Geometry in rendered pixels, including the responsive SVG width. */
export function chartTimeAtPixel(pixel: number, duration: number, scale: number, ratio: number, padding: number): number {
return Math.max(0, Math.min(duration, duration - (pixel / Math.max(ratio, 0.001) - padding) / scale));
}
export function chartPixelAtTime(time: number, duration: number, scale: number, ratio: number, padding: number): number {
return (padding + (duration - Math.max(0, Math.min(duration, time))) * scale) * ratio;
}
/** Fixed-size overview; the final judgement belongs to the last bin. */
export function chartDensity(notes: readonly { timeSeconds: number }[], duration: number, bins = 120): number[] {
const counts = Array<number>(bins).fill(0);
for (const note of notes) {
const index = Math.max(0, Math.min(bins - 1, Math.floor(note.timeSeconds / Math.max(duration, 0.001) * bins)));
counts[index]++;
}
const maximum = Math.max(1, ...counts);
return counts.map(count => count / maximum);
}
export function chartColumnAtTime(time: number, duration: number, secondsPerColumn: number): number {
const count = Math.max(1, Math.ceil(duration / secondsPerColumn));
return Math.max(0, Math.min(count - 1, Math.floor(time / secondsPerColumn)));
}
export function chartColumnContains(time: number, start: number, end: number, duration: number): boolean {
return time >= start && (time < end || (time === duration && end >= duration));
}
/** Use actual SUS boundaries; silent bars and BPM changes still count. */
export function chartMeasureAtTime(measures: readonly { number: number; timeSeconds: number }[], time: number): number | null {
if (measures.length === 0 || time < measures[0].timeSeconds) return null;
let low = 0;
let high = measures.length - 1;
while (low < high) {
const middle = Math.ceil((low + high) / 2);
if (measures[middle].timeSeconds <= time) low = middle;
else high = middle - 1;
}
return measures[low].number;
}