Skip to content

Commit df5310a

Browse files
refactor: generateWaveformPeaksのバケツサイズを引数化し、コメントを整えた
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 88557d0 commit df5310a

3 files changed

Lines changed: 214 additions & 97 deletions

File tree

src/components/Sing/SequencerWaveform.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ const updatePeaksMap = () => {
118118
// NOTE: sequenceIdに対応するAudioBufferは不変なので、既存キーの再計算は不要
119119
for (const [eventId, event] of audioEventsValue) {
120120
if (!tempMap.has(eventId)) {
121-
tempMap.set(eventId, generateWaveformPeaks(event.buffer));
121+
// mipmapの最小のバケツサイズは、最大ズーム時にも解像度が足りる 16 を採用
122+
tempMap.set(eventId, generateWaveformPeaks(event.buffer, 16));
122123
}
123124
}
124125

src/sing/waveformPeaks.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
import { getLast } from "@/sing/utility";
1010

11+
/**
12+
* mipmapのレベル。バケツサイズと、各バケツのmin/max値を保持する。
13+
*/
1114
type WaveformMipmapLevel = {
1215
readonly bucketSize: number;
1316
readonly minBuckets: Float32Array;
@@ -16,7 +19,7 @@ type WaveformMipmapLevel = {
1619

1720
/**
1821
* 波形ピークのmipmap。
19-
* levels[0]が最も細かいレベルで、以降はバケツサイズの昇順に並ぶ。
22+
* 先頭のレベルが最も細かいレベルで、以降はバケツサイズの昇順に並ぶ。
2023
*/
2124
export type WaveformPeaks = readonly WaveformMipmapLevel[];
2225

@@ -28,25 +31,32 @@ export type ResampledPeaks = {
2831
readonly maxValues: Float32Array;
2932
};
3033

31-
const BASE_BUCKET_SIZE = 16;
32-
3334
/**
3435
* AudioBufferのch0からピークのmipmapを生成する。
35-
* 最も細かいレベルのバケツサイズは BASE_BUCKET_SIZE で、
36-
* 以降は2倍ずつ大きなバケツサイズのレベルを、バケツ数が1個になるまで積み上げる。
36+
*
37+
* @param audioBuffer ピークを計算する音声データ。ch0のみ使用される。
38+
* @param minBucketSize 最小のバケツサイズ。1以上の整数でなければならない。
39+
* @returns 生成されたピークのmipmap。レベルはバケツサイズで昇順にソートされている。
3740
*/
38-
export function generateWaveformPeaks(audioBuffer: AudioBuffer): WaveformPeaks {
41+
export function generateWaveformPeaks(
42+
audioBuffer: AudioBuffer,
43+
minBucketSize: number,
44+
): WaveformPeaks {
45+
if (!Number.isInteger(minBucketSize) || minBucketSize < 1) {
46+
throw new Error("minBucketSize must be a positive integer.");
47+
}
48+
3949
const channel = audioBuffer.getChannelData(0);
4050
const numSamples = channel.length;
4151

4252
// レベル0: サンプルから直接バケツ化
43-
const baseNumBuckets = Math.ceil(numSamples / BASE_BUCKET_SIZE);
53+
const baseNumBuckets = Math.ceil(numSamples / minBucketSize);
4454
const baseMinBuckets = new Float32Array(baseNumBuckets);
4555
const baseMaxBuckets = new Float32Array(baseNumBuckets);
4656

4757
for (let bucketIndex = 0; bucketIndex < baseNumBuckets; bucketIndex++) {
48-
const start = bucketIndex * BASE_BUCKET_SIZE;
49-
const end = Math.min(start + BASE_BUCKET_SIZE, numSamples);
58+
const start = bucketIndex * minBucketSize;
59+
const end = Math.min(start + minBucketSize, numSamples);
5060
let min = channel[start];
5161
let max = channel[start];
5262
for (let i = start + 1; i < end; i++) {
@@ -64,13 +74,13 @@ export function generateWaveformPeaks(audioBuffer: AudioBuffer): WaveformPeaks {
6474

6575
const levels: WaveformMipmapLevel[] = [
6676
{
67-
bucketSize: BASE_BUCKET_SIZE,
77+
bucketSize: minBucketSize,
6878
minBuckets: baseMinBuckets,
6979
maxBuckets: baseMaxBuckets,
7080
},
7181
];
7282

73-
// 上位レベル: バケツ数が1個になるまで2バケツずつまとめる
83+
// レベル1以降: バケツ数が1個になるまで2バケツずつまとめる
7484
while (getLast(levels).minBuckets.length > 1) {
7585
const prev = getLast(levels);
7686
const prevNumBuckets = prev.minBuckets.length;
@@ -106,12 +116,15 @@ export function generateWaveformPeaks(audioBuffer: AudioBuffer): WaveformPeaks {
106116
* 各区間に対応するサンプル範囲を境界配列で指定して、ピークをリサンプルする。
107117
* 区間ごとに最適なmipmapレベルを選択するため、サンプル幅が不均等でも適切な解像度になる。
108118
*
109-
* @param binBoundarySamples 長さ `numBins + 1` の配列。
119+
* @param peaks リサンプル元のピークmipmap。
120+
* レベルはバケツサイズで昇順にソートされていなければならない。
121+
* @param binBoundarySamples 各区間の境界をサンプル位置で表した配列。
110122
* `binBoundarySamples[i]` 以上 `binBoundarySamples[i + 1]` 未満が区間 `i` のサンプル範囲。
123+
* 配列の長さは 区間の数 + 1 で、2以上でなければならない。
111124
* 各値は音声データ先頭基準のサンプル位置で、単調非減少でなければならない。
112-
* 負の値や `numSamples` を超える値も許容され、範囲外は0埋めになる。
125+
* サンプル数を超える値や負の値も許容され、範囲外は0埋めになる。
113126
* 幅0の区間は点として扱い、その位置のサンプルを含むバケツの値になる。
114-
* 長さは2以上でなければならない
127+
* @returns 各区間のmin/max値
115128
*/
116129
export function resamplePeaks(
117130
peaks: WaveformPeaks,

0 commit comments

Comments
 (0)