You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将音频电平数组缩放至指定长度以绘制音频波形?

音频电平数组缩放/填充至指定长度的实现问题

我需要实现一个功能:将音频电平(audio meters)数组“填充”或缩放至指定长度,适配固定数量的波形条绘制,具体需求:

  • 当输入数组长度小于目标长度(如12)时,重复输入元素填充至目标长度。例:输入const input = [ -160, -140, -100, -80 ],输出const result = [ -160, -160, -160, -140, -140, -140, -100, -100, -100, -80, -80, -80 ]
  • 当输入数组长度大于目标长度时,按比例合并元素。例:输入20个元素的数组,目标长度12,每个波形条对应约1.67个输入元素,输出示例const result = [ -160, -160, -140, -140, -100, -80, -80, -80, -80, -80, -80, -80 ]

我尝试了两种JavaScript实现,但都有问题:第一种无法返回指定长度的结果数组;第二种无法处理输入长度小于目标长度的情况。请问我的实现思路哪里错了?有没有更优的解决方案?

尝试代码1

const input = [ -160, -160, -160, -140, -140, -140, -100, -100, -100, -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, ];
const MAX_BARS_FOR_AMPLITUDE = 12;
const metersPerBar = input.length / MAX_BARS_FOR_AMPLITUDE;
const barsPerMeter = 1 / metersPerBar;

const { result } = input.reduce(
    (
        acc,
        meter,
    ) => {
        if (barsPerMeter < 1) {
            if (acc.chunkAccumulator.chunks < 1) {
                acc.chunkAccumulator.meterValue ||= meter;
                acc.chunkAccumulator.chunks += barsPerMeter;
            } else {
                acc.result.push(acc.chunkAccumulator.meterValue);

                acc.chunkAccumulator.meterValue = 0;
                acc.chunkAccumulator.chunks = 0;
            }
        } else {
            if (acc.chunkAccumulator.chunks < barsPerMeter) {
                acc.chunkAccumulator.meterValue ||= meter;
                acc.chunkAccumulator.chunks += barsPerMeter;
            } else {
                acc.result.push(acc.chunkAccumulator.meterValue);

                acc.chunkAccumulator.meterValue = 0;
                acc.chunkAccumulator.chunks = 0;
            }
        }

        return acc;
    },
    {
        result: [],
        chunkAccumulator: {
            meterValue: 0,
            chunks: 0,
        },
    },
);

console.log(result);

尝试代码2

const input = [ -160, -160, -160, -140, -140, -140, -100, -100, -100, -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, ];

const MAX_BARS_FOR_AMPLITUDE = 40;
const metersPerBar = input.length / MAX_BARS_FOR_AMPLITUDE;
const barsPerMeter = 1 / metersPerBar;

let currentPoint = 0;
let currentChunk = 0;
const result = Array.from({ length: MAX_BARS_FOR_AMPLITUDE }, (_, index) => {
    if (barsPerMeter > currentChunk) {
        currentChunk += barsPerMeter;
    } else {
        currentPoint++;
        currentChunk = 0
    }

    return input[currentPoint];
});

console.log(result);

解决方案与问题分析

现有代码的问题

  1. 代码1的问题:

    • 使用reduce遍历输入数组时,仅在累加器达到阈值时才向结果数组push元素,最后可能遗漏未完成的累加块,导致结果长度不足目标值。
    • 处理输入长度小于目标长度的场景时(即barsPerMeter > 1),逻辑错误:每次累加barsPerMeter,但没有处理需要重复推送同一元素的情况,无法生成更长的结果数组。
  2. 代码2的问题:

    • 仅处理了输入长度大于目标长度的情况,当输入更短时,barsPerMeter > 1,会不断累加currentChunk导致currentPoint不会递增,结果数组会重复输入的第一个元素,不符合需求。

更优实现思路

核心逻辑是为目标数组的每个位置,计算其对应输入数组的索引:

  • 当输入长度 < 目标长度:每个输入元素需要被重复目标长度/输入长度次,取每个目标位置对应的输入索引(Math.floor((目标索引 * 输入长度) / 目标长度))。
  • 当输入长度 > 目标长度:每个目标位置对应输入数组的一段元素,取这段元素的代表值(示例中取的是起始值,也可根据需求取平均值、最大值等)。

以下是兼容两种场景的实现:

function resizeAudioMeters(input, targetLength) {
    const inputLength = input.length;
    if (inputLength === 0 || targetLength === 0) return [];
    
    return Array.from({ length: targetLength }, (_, targetIndex) => {
        // 计算当前目标索引对应的输入数组位置比例
        const ratio = targetIndex / targetLength;
        // 映射到输入数组的索引(取整)
        const inputIndex = Math.floor(ratio * inputLength);
        return input[inputIndex];
    });
}

// 测试输入长度小于目标长度的情况
const input1 = [ -160, -140, -100, -80 ];
console.log(resizeAudioMeters(input1, 12));
// 输出: [ -160, -160, -160, -140, -140, -140, -100, -100, -100, -80, -80, -80 ]

// 测试输入长度大于目标长度的情况
const input2 = [ -160, -160, -160, -140, -140, -140, -100, -100, -100, -80, -80, -80, -80, -80, -80, -80, -80, -80, -80, -80 ];
console.log(resizeAudioMeters(input2, 12));
// 输出: [ -160, -160, -140, -140, -100, -80, -80, -80, -80, -80, -80, -80 ]

扩展说明

如果需要在合并元素时取平均值(而非起始值),可以修改逻辑为:

function resizeAudioMetersWithAverage(input, targetLength) {
    const inputLength = input.length;
    if (inputLength === 0 || targetLength === 0) return [];
    
    return Array.from({ length: targetLength }, (_, targetIndex) => {
        const startRatio = targetIndex / targetLength;
        const endRatio = (targetIndex + 1) / targetLength;
        const startIndex = Math.floor(startRatio * inputLength);
        const endIndex = Math.min(Math.ceil(endRatio * inputLength), inputLength);
        
        // 计算区间内元素的平均值
        const sum = input.slice(startIndex, endIndex).reduce((acc, val) => acc + val, 0);
        return sum / (endIndex - startIndex);
    });
}

内容的提问来源于stack exchange,提问作者Mike K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 23:34:50