began soundfont implementation

This commit is contained in:
Felix Roos 2022-06-20 20:02:55 +02:00
parent b353db67d4
commit 82d6103907
8 changed files with 2144 additions and 263 deletions

View file

@ -0,0 +1,13 @@
// this script converts a soundfont into a json file, it has not been not used yet
import fetch from 'node-fetch';
const name = '0000_JCLive';
const js = await fetch(`https://surikov.github.io/webaudiofontdata/sound/${name}_sf2_file.js`).then((res) =>
res.text(),
);
// console.log(js);
let [_, data] = js.split('_sf2_file=');
data = eval(data);
console.log(JSON.stringify(data));

View file

@ -0,0 +1,108 @@
let loadCache = {};
async function loadFont(name) {
if (loadCache[name]) {
return loadCache[name];
}
const load = async () => {
// TODO: make soundfont source configurable
const url = `https://surikov.github.io/webaudiofontdata/sound/${name}.js`;
console.log('load font', name, url);
const preset = await fetch(url).then((res) => res.text());
let [_, data] = preset.split('={');
return eval('{' + data);
};
loadCache[name] = load();
return loadCache[name];
}
let bufferCache = {};
export async function getFontBufferSource(name, pitch, ac) {
const key = `${name}:::${pitch}`;
if (bufferCache[key]) {
return (await bufferCache[key])();
}
// console.log('load buffer', key);
const load = async () => {
const preset = await loadFont(name);
if (!preset) {
throw new Error(`Could not load soundfont ${name}`);
}
const zone = findZone(preset, pitch);
if (!zone) {
throw new Error('no soundfont zone found for preset ', name, 'pitch', pitch);
}
const buffer = await getBuffer(zone, ac);
if (!buffer) {
throw new Error(`no soundfont buffer found for preset ${name}, pitch: ${pitch}`);
}
return () => {
const src = ac.createBufferSource();
src.buffer = buffer;
const baseDetune = zone.originalPitch - 100.0 * zone.coarseTune - zone.fineTune;
const playbackRate = 1.0 * Math.pow(2, (100.0 * pitch - baseDetune) / 1200.0);
// src detune?
src.playbackRate.value = playbackRate;
const loop = zone.loopStart > 1 && zone.loopStart < zone.loopEnd;
if (!loop) {
/* const waveDuration = duration + this.afterTime;
if (waveDuration > zone.buffer.duration / playbackRate) {
waveDuration = zone.buffer.duration / playbackRate;
// TODO: do sth with waveduration
} */
} else {
src.loop = true;
/* src.loopStart = zone.loopStart / zone.sampleRate + (zone.delay ? zone.delay : 0);
src.loopEnd = zone.loopEnd / zone.sampleRate + (zone.delay ? zone.delay : 0); */
}
return src;
};
};
bufferCache[key] = load(); // dont await here to cache promise immediately!
return (await bufferCache[key])();
}
function findZone(preset, pitch) {
return preset.find((zone) => {
return zone.keyRangeLow <= pitch && zone.keyRangeHigh + 1 >= pitch;
});
}
// promisified version of https://github.com/felixroos/webaudiofont/blob/c6f97249b60dcfafc20fca5bb381294a6b2f8f51/npm/dist/WebAudioFontPlayer.js#L740
async function getBuffer(zone, audioContext) {
if (zone.sample) {
console.warn('zone.sample untested!');
const decoded = atob(zone.sample);
zone.buffer = audioContext.createBuffer(1, decoded.length / 2, zone.sampleRate);
const float32Array = zone.buffer.getChannelData(0);
let b1, b2, n;
for (var i = 0; i < decoded.length / 2; i++) {
b1 = decoded.charCodeAt(i * 2);
b2 = decoded.charCodeAt(i * 2 + 1);
if (b1 < 0) {
b1 = 256 + b1;
}
if (b2 < 0) {
b2 = 256 + b2;
}
n = b2 * 256 + b1;
if (n >= 65536 / 2) {
n = n - 65536;
}
float32Array[i] = n / 65536.0;
}
} else {
if (zone.file) {
const datalen = zone.file.length;
const arraybuffer = new ArrayBuffer(datalen);
const view = new Uint8Array(arraybuffer);
const decoded = atob(zone.file);
let b;
for (let i = 0; i < decoded.length; i++) {
b = decoded.charCodeAt(i);
view[i] = b;
}
return new Promise((resolve) => audioContext.decodeAudioData(arraybuffer, resolve));
}
}
}

View file

@ -1,29 +1,6 @@
import { Pattern } from '@strudel.cycles/core';
import { getAudioContext } from '@strudel.cycles/webaudio';
// import * as WebAudioFontPlayer from 'webaudiofont';
import * as WebAudioFont from 'webaudiofont';
console.log('WebAudioFont:!', WebAudioFont);
// https://surikov.github.io/webaudiofont/npm/dist/WebAudioFontPlayer.js
// https://surikov.github.io/webaudiofontdata/sound/0000_JCLive_sf2_file.js
import { getFontBufferSource } from './fontloader.mjs';
import * as soundfontList from './list.mjs';
// https://github.com/surikov/webaudiofont#dynamic-loading
/* function changeInstrument(path, name) {
player.loader.startLoad(audioContext, path, name);
player.loader.waitLoad(function () {
instr = window[name];
});
} */
Pattern.prototype.soundfont = function (soundfont) {
const ac = getAudioContext();
return this.onTrigger((t, hap, ct) => {
// const url = `https://surikov.github.io/webaudiofontdata/sound/${soundfont}_sf2_file.js`;
// changeInstrument('https://surikov.github.io/webaudiofontdata/sound/0290_Aspirin_sf2_file.js','_tone_0290_Aspirin_sf2_file');
console.log('url', url);
// const player = WebAudioFontPlayer();
// console.log('soundfont', url, player);
/* var selectedPreset = _tone_0000_JCLive_sf2_file;
player.loader.decodeAfterLoading(ac, '_tone_0000_JCLive_sf2_file');
player.queueWaveTable(ac, ac.destination, selectedPreset, 0, 55, 3.5); */
});
};
globalThis.getFontBufferSource = getFontBufferSource;
globalThis.soundfontList = soundfontList;
globalThis.soundfontList = soundfontList;

1767
packages/soundfonts/list.mjs Normal file

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
@ -24,8 +25,10 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"webaudiofont": "^3.0.1",
"@strudel.cycles/core": "*",
"@strudel.cycles/webaudio": "*"
},
"devDependencies": {
"node-fetch": "^3.2.6"
}
}