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

如何导出Tone.Compressor压缩音频并用wavesurfer.js绘图?Tone.js能否实现信号图?

如何用WaveSurfer.js绘制Tone.Compressor处理后的音频波形?

嘿,我来帮你搞定Tone.js和WaveSurfer.js的衔接问题!你已经用Tone.Compressor处理了音频,现在要让WaveSurfer画出处理后的波形,核心就是把Tone处理后的音频数据转成WaveSurfer能识别的格式就行,我分两种常见场景给你拆解步骤:

核心思路

Tone.js的Compressor是Web Audio API里的音频处理节点,它会实时处理音频流,但WaveSurfer需要的是完整的音频文件(或Blob、音频数据数组)才能绘制波形。所以我们的目标就是把经过Compressor处理后的音频导出成WaveSurfer支持的格式,再交给它渲染。


场景1:预加载本地/网络音频文件,处理后绘制波形

如果是已经存在的音频文件,我们可以用Tone.Offline把处理后的音频渲染成AudioBuffer,再转成Blob传给WaveSurfer:

// 初始化Tone.Compressor,用你配置的参数
const comp = new Tone.Compressor({
  ratio: 12,
  threshold: -60,
  release: 0.25,
  attack: 0.001,
  knee: 30
});

// 加载目标音频文件(这里用Tone.Sampler举例,也可以用Tone.Player)
const sampler = new Tone.Sampler({
  urls: {
    "C4": "your-audio-file.mp3" // 替换成你的音频文件路径
  },
  onload: async () => {
    // 连接节点:音频源 -> 压缩器
    sampler.connect(comp);
    
    // 使用Tone.Offline渲染处理后的音频,第二个参数是音频时长(秒),要和你的音频匹配
    const processedBuffer = await Tone.Offline(() => {
      sampler.start(0); // 从头播放音频
    }, 4); // 示例时长4秒,根据实际音频调整
    
    // 把AudioBuffer转成WAV格式的Blob(WaveSurfer支持直接加载Blob)
    const audioBlob = audioBufferToBlob(processedBuffer);
    
    // 初始化WaveSurfer
    const wavesurfer = WaveSurfer.create({
      container: '#waveform', // 页面中用来放波形的DOM元素ID
      waveColor: 'deepskyblue',
      progressColor: 'darkblue'
    });
    
    // 加载Blob并绘制波形
    wavesurfer.loadBlob(audioBlob);
  }
});

// 辅助工具函数:把AudioBuffer转成WAV Blob
function audioBufferToBlob(buffer) {
  const channelCount = buffer.numberOfChannels;
  const bufferLength = buffer.length * channelCount * 2 + 44;
  const arrayBuffer = new ArrayBuffer(bufferLength);
  const view = new DataView(arrayBuffer);
  const channels = [];
  let offset = 0;
  let pos = 0;

  // 写入WAV文件头
  const writeUint16 = (val) => {
    view.setUint16(pos, val, true);
    pos += 2;
  };
  const writeUint32 = (val) => {
    view.setUint32(pos, val, true);
    pos += 4;
  };

  writeUint32(0x46464952); // "RIFF"
  writeUint32(bufferLength - 8); // 文件总长度减去8
  writeUint32(0x45564157); // "WAVE"

  writeUint32(0x20746d66); // "fmt "
  writeUint32(16); // PCM格式子块长度
  writeUint16(1); // 编码格式:PCM
  writeUint16(channelCount);
  writeUint32(buffer.sampleRate);
  writeUint32(buffer.sampleRate * 2 * channelCount); // 字节率
  writeUint16(channelCount * 2); // 块对齐
  writeUint16(16); // 位深度:16位

  writeUint32(0x61746164); // "data"
  writeUint32(bufferLength - pos - 4); // 音频数据长度

  // 写入音频采样数据
  for (let i = 0; i < channelCount; i++) {
    channels.push(buffer.getChannelData(i));
  }

  while (pos < bufferLength) {
    for (let i = 0; i < channelCount; i++) {
      // 把浮点数采样值转成16位整数
      let sample = Math.max(-1, Math.min(1, channels[i][offset]));
      sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
      view.setInt16(pos, sample, true);
      pos += 2;
    }
    offset++;
  }

  return new Blob([arrayBuffer], { type: 'audio/wav' });
}

场景2:实时处理音频(比如麦克风输入)并绘制波形

如果是实时音频输入(比如麦克风),我们可以用MediaRecorder捕获Compressor处理后的音频流,录制完成后转成Blob给WaveSurfer:

// 初始化压缩器
const comp = new Tone.Compressor({
  ratio: 12,
  threshold: -60,
  release: 0.25,
  attack: 0.001,
  knee: 30
});

// 获取麦克风音频流
navigator.mediaDevices.getUserMedia({ audio: true })
  .then(stream => {
    // 创建Tone的媒体流源节点,连接到压缩器
    const audioInput = new Tone.MediaStreamSource(stream);
    audioInput.connect(comp);
    
    // 创建MediaStreamDestination来捕获处理后的音频
    const outputDest = Tone.context.createMediaStreamDestination();
    comp.connect(outputDest);
    
    // 初始化MediaRecorder录制处理后的音频
    const recorder = new MediaRecorder(outputDest.stream);
    const audioChunks = [];
    
    // 收集录制的音频片段
    recorder.ondataavailable = (e) => {
      audioChunks.push(e.data);
    };
    
    // 录制完成后,把片段合并成Blob并交给WaveSurfer
    recorder.onstop = () => {
      const processedBlob = new Blob(audioChunks, { type: 'audio/wav' });
      // 初始化WaveSurfer
      const wavesurfer = WaveSurfer.create({
        container: '#waveform',
        waveColor: 'coral',
        progressColor: 'crimson'
      });
      wavesurfer.loadBlob(processedBlob);
    };
    
    // 开始录制,示例录制5秒后停止
    recorder.start();
    setTimeout(() => recorder.stop(), 5000);
  })
  .catch(err => console.error('麦克风访问失败:', err));

额外提示

  • 如果你之前用Tone.Analyser做的是实时动态可视化,WaveSurfer则是绘制完整音频的静态波形,两者的应用场景不同,按需选择即可。
  • Tone.Offline的时长参数一定要和你的音频实际时长匹配,不然会出现音频截断或补空的问题。
  • 确保页面里已经正确引入了Tone.js和WaveSurfer.js的脚本文件哦~

内容的提问来源于stack exchange,提问作者Disha Purohit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:51:30