如何将音频电平数组缩放至指定长度以绘制音频波形?
音频电平数组缩放/填充至指定长度的实现问题
我需要实现一个功能:将音频电平(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的问题:
- 使用
reduce遍历输入数组时,仅在累加器达到阈值时才向结果数组push元素,最后可能遗漏未完成的累加块,导致结果长度不足目标值。 - 处理输入长度小于目标长度的场景时(即
barsPerMeter > 1),逻辑错误:每次累加barsPerMeter,但没有处理需要重复推送同一元素的情况,无法生成更长的结果数组。
- 使用
代码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
相关产品推荐
相关产品推荐

