From c08b4e60a781d5c691fdfc38e195f20dd7151a98 Mon Sep 17 00:00:00 2001 From: Sig <62321214+sigprogramming@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:24:39 +0900 Subject: [PATCH] =?UTF-8?q?main=E3=83=96=E3=83=A9=E3=83=B3=E3=83=81?= =?UTF-8?q?=E3=83=9E=E3=83=BC=E3=82=B8=E5=BE=8C=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ScoreSequencer.vue | 6 +- src/components/Sing/SequencerPitch.vue | 6 +- src/sing/utility.ts | 243 +++++++++++++------------ src/store/singing.ts | 6 +- 4 files changed, 132 insertions(+), 129 deletions(-) diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index b8eb1ff61c..0583add129 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -217,7 +217,7 @@ import { useCommandOrControlKey, useShiftKey, } from "@/composables/useModifierKey"; -import { applyGaussianFilter, Interpolate } from "@/sing/utility"; +import { applyGaussianFilter, interpolateLinear } from "@/sing/utility"; import { useLyricInput } from "@/composables/useLyricInput"; import { useCursorState, CursorState } from "@/composables/useCursorState"; import { ExhaustiveError } from "@/type/utility"; @@ -667,7 +667,7 @@ const previewDrawPitch = () => { } else if (cursorFrame < prevCursorPos.frame) { for (let i = cursorFrame; i <= prevCursorPos.frame; i++) { tempPitchEdit.data[i - tempPitchEdit.startFrame] = Math.exp( - Interpolate.linear( + interpolateLinear( { x: cursorFrame, y: Math.log(cursorFrequency) }, { x: prevCursorPos.frame, y: Math.log(prevCursorPos.frequency) }, i, @@ -677,7 +677,7 @@ const previewDrawPitch = () => { } else { for (let i = prevCursorPos.frame; i <= cursorFrame; i++) { tempPitchEdit.data[i - tempPitchEdit.startFrame] = Math.exp( - Interpolate.linear( + interpolateLinear( { x: prevCursorPos.frame, y: Math.log(prevCursorPos.frequency) }, { x: cursorFrame, y: Math.log(cursorFrequency) }, i, diff --git a/src/components/Sing/SequencerPitch.vue b/src/components/Sing/SequencerPitch.vue index 6eeb94a487..c072607867 100644 --- a/src/components/Sing/SequencerPitch.vue +++ b/src/components/Sing/SequencerPitch.vue @@ -29,7 +29,7 @@ import { } from "@/composables/onMountOrActivate"; import { ExhaustiveError } from "@/type/utility"; import { createLogger } from "@/domain/frontend/log"; -import { Interpolate, iterativeEndPointFit } from "@/sing/utility"; +import { interpolatePchip, iterativeEndPointFit } from "@/sing/utility"; import { Color } from "@/sing/graphics/color"; import { Points } from "@/sing/graphics/points"; import { getLast } from "@/sing/utility"; @@ -416,8 +416,6 @@ const generateOriginalPitchData = () => { }; const generatePitchEditData = () => { - const frameRate = editorFrameRate.value; - const tempData = [...pitchEditData.value]; // プレビュー中のピッチ編集があれば、適用する if (previewPitchEdit.value != undefined) { @@ -520,7 +518,7 @@ watch( xValues.push(i); } xValues.push(maxX); - const yValues = Interpolate.catmullRom(points2, xValues); + const yValues = interpolatePchip(points2, xValues); const interpPitchData: PitchData = { ticksArray: xValues, data: yValues.map((value) => noteNumberToFrequency(value)), diff --git a/src/sing/utility.ts b/src/sing/utility.ts index ed9ee117fa..1f1c27449c 100644 --- a/src/sing/utility.ts +++ b/src/sing/utility.ts @@ -22,142 +22,147 @@ export function calculateDistanceFromPointToLine( ); } -export class Interpolate { - static linear( - p0: { x: number; y: number }, - p1: { x: number; y: number }, - x: number, - ) { - if (p1.x <= p0.x) { - throw new Error("p1.x must be greater than p0.x."); - } - const m = (p1.y - p0.y) / (p1.x - p0.x); - return p0.y + (x - p0.x) * m; +export function interpolateLinear( + p0: { x: number; y: number }, + p1: { x: number; y: number }, + x: number, +) { + if (p1.x <= p0.x) { + throw new Error("p1.x must be greater than p0.x."); } + const m = (p1.y - p0.y) / (p1.x - p0.x); + return p0.y + (x - p0.x) * m; +} - static cubicHermite( - p0: { x: number; y: number }, - m0: number, - p1: { x: number; y: number }, - m1: number, - x: number, - ) { - const dx = p1.x - p0.x; - const t = (x - p0.x) / dx; - const h0 = 2 * t ** 3 - 3 * t ** 2 + 1; - const h1 = t ** 3 - 2 * t ** 2 + t; - const h2 = -2 * t ** 3 + 3 * t ** 2; - const h3 = t ** 3 - t ** 2; - return p0.y * h0 + m0 * dx * h1 + p1.y * h2 + m1 * dx * h3; - } +export function interpolateCubicHermite( + p0: { x: number; y: number }, + m0: number, + p1: { x: number; y: number }, + m1: number, + x: number, +) { + const dx = p1.x - p0.x; + const t = (x - p0.x) / dx; + const h0 = 2 * t ** 3 - 3 * t ** 2 + 1; + const h1 = t ** 3 - 2 * t ** 2 + t; + const h2 = -2 * t ** 3 + 3 * t ** 2; + const h3 = t ** 3 - t ** 2; + return p0.y * h0 + m0 * dx * h1 + p1.y * h2 + m1 * dx * h3; +} - static catmullRom(points: { x: number; y: number }[], xValues: number[]) { - if (points.length < 2) { - throw new Error("points.length must be at least 2."); - } - const n = points.length; - const firstP = points[0]; - const lastP = points[n - 1]; +export function interpolateCubicSpline( + points: { x: number; y: number }[], + xValues: number[], +) { + if (points.length < 2) { + throw new Error("points.length must be at least 2."); + } + const n = points.length; + const firstP = points[0]; + const lastP = points[n - 1]; - const mValues: number[] = []; - for (let i = 0; i < n; i++) { - const p0 = points[Math.max(0, i - 1)]; - const p1 = points[Math.min(n - 1, i + 1)]; - const m = (p1.y - p0.y) / (p1.x - p0.x); - mValues.push(m); - } + const mValues: number[] = []; + for (let i = 0; i < n; i++) { + const p0 = points[Math.max(0, i - 1)]; + const p1 = points[Math.min(n - 1, i + 1)]; + const m = (p1.y - p0.y) / (p1.x - p0.x); + mValues.push(m); + } - const yValues: number[] = []; - for (const x of xValues) { - if (x < firstP.x) { - const m = mValues[0]; - const y = firstP.y + (x - firstP.x) * m; - yValues.push(y); - } else if (x >= lastP.x) { - const m = mValues[n - 1]; - const y = lastP.y + (x - lastP.x) * m; - yValues.push(y); - } else { - for (let i = 0; i < n - 1; i++) { - if (x < points[i + 1].x) { - const p0 = points[i]; - const p1 = points[i + 1]; - const m0 = mValues[i]; - const m1 = mValues[i + 1]; - const y = this.cubicHermite(p0, m0, p1, m1, x); - yValues.push(y); - break; - } + const yValues: number[] = []; + for (const x of xValues) { + if (x < firstP.x) { + const m = mValues[0]; + const y = firstP.y + (x - firstP.x) * m; + yValues.push(y); + } else if (x >= lastP.x) { + const m = mValues[n - 1]; + const y = lastP.y + (x - lastP.x) * m; + yValues.push(y); + } else { + for (let i = 0; i < n - 1; i++) { + if (x < points[i + 1].x) { + const p0 = points[i]; + const p1 = points[i + 1]; + const m0 = mValues[i]; + const m1 = mValues[i + 1]; + const y = interpolateCubicHermite(p0, m0, p1, m1, x); + yValues.push(y); + break; } } } - return yValues; } + return yValues; +} - static pchip(points: { x: number; y: number }[], xValues: number[]) { - if (points.length < 2) { - throw new Error("points.length must be at least 2."); - } - const n = points.length; - const firstP = points[0]; - const lastP = points[n - 1]; +export function interpolatePchip( + points: { x: number; y: number }[], + xValues: number[], +) { + if (points.length < 2) { + throw new Error("points.length must be at least 2."); + } + const n = points.length; + const firstP = points[0]; + const lastP = points[n - 1]; - const mValues: number[] = []; - for (let i = 0; i < n; i++) { - const p0 = points[Math.max(0, i - 1)]; - const p1 = points[i]; - const p2 = points[Math.min(n - 1, i + 1)]; - const dx = p2.x - p0.x; - const dy0 = p1.y - p0.y; - const dy1 = p2.y - p1.y; - const m = dy0 * dy1 <= 0 ? 0 : (dy0 + dy1) / dx; - mValues.push(m); - } - for (let i = 0; i < n - 1; i++) { - const m0 = mValues[i]; - const m1 = mValues[i + 1]; - const p0 = points[i]; - const p1 = points[i + 1]; - const dx = p1.x - p0.x; - const dy = p1.y - p0.y; - if (dy !== 0) { - const d = dy / dx; - const a = m0 / d; - const b = m1 / d; - const t = 3 / Math.sqrt(a * a + b * b); - if (t < 1) { - mValues[i] = t * a * d; - mValues[i + 1] = t * b * d; - } + const mValues: number[] = []; + for (let i = 0; i < n; i++) { + const p0 = points[Math.max(0, i - 1)]; + const p1 = points[i]; + const p2 = points[Math.min(n - 1, i + 1)]; + const dx = p2.x - p0.x; + const dy0 = p1.y - p0.y; + const dy1 = p2.y - p1.y; + const m = dy0 * dy1 <= 0 ? 0 : (dy0 + dy1) / dx; + mValues.push(m); + } + for (let i = 0; i < n - 1; i++) { + const m0 = mValues[i]; + const m1 = mValues[i + 1]; + const p0 = points[i]; + const p1 = points[i + 1]; + const dx = p1.x - p0.x; + const dy = p1.y - p0.y; + if (dy !== 0) { + const d = dy / dx; + const a = m0 / d; + const b = m1 / d; + const n = Math.sqrt(a * a + b * b); + if (n > 3) { + const t = 3 / n; + mValues[i] = t * a * d; + mValues[i + 1] = t * b * d; } } + } - const yValues: number[] = []; - for (const x of xValues) { - if (x < firstP.x) { - const m = mValues[0]; - const y = firstP.y + (x - firstP.x) * m; - yValues.push(y); - } else if (x >= lastP.x) { - const m = mValues[n - 1]; - const y = lastP.y + (x - lastP.x) * m; - yValues.push(y); - } else { - for (let i = 0; i < n - 1; i++) { - if (x < points[i + 1].x) { - const p0 = points[i]; - const p1 = points[i + 1]; - const m0 = mValues[i]; - const m1 = mValues[i + 1]; - const y = this.cubicHermite(p0, m0, p1, m1, x); - yValues.push(y); - break; - } + const yValues: number[] = []; + for (const x of xValues) { + if (x < firstP.x) { + const m = mValues[0]; + const y = firstP.y + (x - firstP.x) * m; + yValues.push(y); + } else if (x >= lastP.x) { + const m = mValues[n - 1]; + const y = lastP.y + (x - lastP.x) * m; + yValues.push(y); + } else { + for (let i = 0; i < n - 1; i++) { + if (x < points[i + 1].x) { + const p0 = points[i]; + const p1 = points[i + 1]; + const m0 = mValues[i]; + const m1 = mValues[i + 1]; + const y = interpolateCubicHermite(p0, m0, p1, m1, x); + yValues.push(y); + break; } } } - return yValues; } + return yValues; } export function differentiate(yValues: number[]) { @@ -196,7 +201,7 @@ export function iterativeEndPointFit( let farthestPointD = 0; for (let i = 1; i < pointsToProcess.length - 1; i++) { const p2 = pointsToProcess[i]; - const d = Math.abs(p2.y - Interpolate.linear(p0, p1, p2.x)); + const d = Math.abs(p2.y - interpolateLinear(p0, p1, p2.x)); if (d > farthestPointD) { farthestPointD = d; farthestPointIndex = i; diff --git a/src/store/singing.ts b/src/store/singing.ts index 2752128be6..fe44f4f4bc 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -93,7 +93,7 @@ import { AnimationTimer, calculateHash, createPromiseThatResolvesWhen, - Interpolate, + interpolateLinear, round, } from "@/sing/utility"; import { getWorkaroundKeyRangeAdjustment } from "@/sing/workaroundKeyRangeAdjustment"; @@ -378,10 +378,10 @@ const muteLastPauSection = ( volume[lastPauStartFrame] *= 0.5; } else { for (let i = 0; i < fadeOutFrameLength; i++) { - volume[lastPauStartFrame + i] *= Interpolate.linear( + volume[lastPauStartFrame + i] *= interpolateLinear( { x: 0, y: 1 }, { x: fadeOutFrameLength - 1, y: 0 }, - i + i, ); } }