began soundfont implementation
This commit is contained in:
parent
b353db67d4
commit
82d6103907
8 changed files with 2144 additions and 263 deletions
13
packages/soundfonts/convert.js
Normal file
13
packages/soundfonts/convert.js
Normal 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));
|
||||
108
packages/soundfonts/fontloader.mjs
Normal file
108
packages/soundfonts/fontloader.mjs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
1767
packages/soundfonts/list.mjs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue