如何从SF2音色库提取样本适配Tone.js?解码报错求助
在Tone.js中加载SoundFont(.sf2)样本时解码音频数据失败的问题
已成功使用sf2-parser解析SoundFont文件,提取到ArrayBuffer格式的样本数据,但调用AudioContext.decodeAudioData()时抛出EncodingError: Unable to decode audio data错误,无法将样本适配到Tone.js Sampler中使用。
现有代码
加载与提取SF2样本的代码
import { sf2 } from '../scripts/sf2-parser'; export const loadSoundFont = async (url: string): Promise<sf2.Parser> => { const response = await fetch(url); if (response.ok) { const parser = new sf2.Parser(new Uint8Array(await response.arrayBuffer()), { parserOptions: { isSf3: false }, isSf3: false }) parser.parse() return parser } else { throw `Failed to load soundfont file from URL: ${url}` } } export const getSample = (parser: any, note: string): ArrayBuffer => { const sampleIndex = parser.sampleHeader.findIndex((s: any) => s.sampleName.startsWith(note)); if (sampleIndex < 0) { throw new Error(`Sample for note ${note} not found`); } const sampleData = parser.sample[sampleIndex]; return sampleData; }
Tone.js中使用样本的代码
const soundFont = await loadSoundFont('./src/assets/soundfonts/SMK_Style_Synth_Brass_Recreation.sf2') const sampleArrayBuffer = getSample(soundFont, 'Synth Brass C4L') const audioContext = Tone.getContext() audioContext.resume() const audioBuffer = await audioContext.decodeAudioData(sampleArrayBuffer) sampler = new Tone.Sampler({ urls: { C4: audioBuffer, }, }).toDestination()
问题原因
sf2-parser提取的是原始PCM采样数据,不包含WAV/MP3等标准音频格式的文件头信息。而decodeAudioData()仅能解码带有格式头的封装音频数据,无法直接识别原始PCM,因此抛出解码错误。
解决方案
需要从SF2样本头中提取采样参数,手动创建AudioBuffer并填充转换后的PCM数据:
修改样本提取函数,获取采样信息
export const getSampleAndInfo = (parser: any, note: string) => { const sampleIndex = parser.sampleHeader.findIndex((s: any) => s.sampleName.startsWith(note)); if (sampleIndex < 0) { throw new Error(`Sample for note ${note} not found`); } const sampleData = parser.sample[sampleIndex]; const sampleHeader = parser.sampleHeader[sampleIndex]; return { data: sampleData, sampleRate: sampleHeader.sampleRate, bitDepth: sampleHeader.sampleSize, // SF2样本通常为16位 channels: 1 // SF2样本默认单声道,左右声道为独立样本 }; }
手动创建AudioBuffer并填充数据
const soundFont = await loadSoundFont('./src/assets/soundfonts/SMK_Style_Synth_Brass_Recreation.sf2') const { data: sampleArrayBuffer, sampleRate, bitDepth, channels } = getSampleAndInfo(soundFont, 'Synth Brass C4L') const audioContext = Tone.getContext() audioContext.resume() // 根据采样参数创建空的AudioBuffer const audioBuffer = audioContext.createBuffer( channels, sampleArrayBuffer.byteLength / (bitDepth / 8), // 计算采样点数量 sampleRate ); // 将原始PCM数据转换为AudioBuffer所需的浮点格式(-1到1之间) const channelData = audioBuffer.getChannelData(0); const dataView = new DataView(sampleArrayBuffer); let byteOffset = 0; // 处理16位有符号整数格式的PCM数据(SF2标准格式) if (bitDepth === 16) { for (let i = 0; i < channelData.length; i++) { // 读取16位有符号整数,转换为浮点值 channelData[i] = dataView.getInt16(byteOffset, true) / 32768; byteOffset += 2; } } // 初始化Tone.js Sampler sampler = new Tone.Sampler({ urls: { C4: audioBuffer, }, }).toDestination()
补充说明
- SF2格式中,立体声乐器的左右声道是独立的样本(如
C4L和C4R),若需要立体声输出,需分别提取两个样本并合并到双声道的AudioBuffer中。 - 若样本位深度为8位,需调整转换逻辑:将无符号整数转换为浮点值(
dataView.getUint8(byteOffset) / 127 - 1)。
内容的提问来源于stack exchange,提问作者apps2go
相关产品推荐
相关产品推荐

