如何导出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
相关产品推荐
相关产品推荐

