Added examples, fixed samplerate issue on import, added to settings tab, fixed spread, added default wavetables, change default phaserand to 0
This commit is contained in:
parent
8b2c35b7a3
commit
97beaec25a
8 changed files with 311 additions and 38 deletions
|
|
@ -93,7 +93,8 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound');
|
||||||
* @name wtPos
|
* @name wtPos
|
||||||
* @param {number | Pattern} position Position in the wavetable from 0 to 1
|
* @param {number | Pattern} position Position in the wavetable from 0 to 1
|
||||||
* @synonyms wavetablePosition
|
* @synonyms wavetablePosition
|
||||||
*
|
* @example
|
||||||
|
* s("squelch").seg(8).note("F1").wtPos("0 0.25 0.5 0.75 1")
|
||||||
*/
|
*/
|
||||||
export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetablePosition');
|
export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetablePosition');
|
||||||
|
|
||||||
|
|
@ -103,7 +104,9 @@ export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetableP
|
||||||
* @name wtWarp
|
* @name wtWarp
|
||||||
* @param {number | Pattern} amount Warp of the wavetable from 0 to 1
|
* @param {number | Pattern} amount Warp of the wavetable from 0 to 1
|
||||||
* @synonyms wavetableWarp
|
* @synonyms wavetableWarp
|
||||||
*
|
* @example
|
||||||
|
* s("basique").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1")
|
||||||
|
* .wtWarpMode("spin")
|
||||||
*/
|
*/
|
||||||
export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp');
|
export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp');
|
||||||
|
|
||||||
|
|
@ -116,6 +119,9 @@ export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWar
|
||||||
* @name wtWarpMode
|
* @name wtWarpMode
|
||||||
* @param {number | string | Pattern} mode Warp mode
|
* @param {number | string | Pattern} mode Warp mode
|
||||||
* @synonyms wavetableWarpMode
|
* @synonyms wavetableWarpMode
|
||||||
|
* @example
|
||||||
|
* s("morgana").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1")
|
||||||
|
* .wtWarpMode("<asym bendp spin logistic sync wormhole brownian>*2")
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { wtWarpMode, wavetableWarpMode } = registerControl('wtWarpMode', 'wavetableWarpMode');
|
export const { wtWarpMode, wavetableWarpMode } = registerControl('wtWarpMode', 'wavetableWarpMode');
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,7 @@ export const WarpMode = Object.freeze({
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadWavetableFrames(url, label, frameLen = 256) {
|
async function loadWavetableFrames(url, label, frameLen = 256) {
|
||||||
const ac = getAudioContext();
|
const buf = await loadBuffer(url, label);
|
||||||
const buf = await loadBuffer(url, ac, label);
|
|
||||||
const ch0 = buf.getChannelData(0);
|
const ch0 = buf.getChannelData(0);
|
||||||
const total = ch0.length;
|
const total = ch0.length;
|
||||||
const numFrames = Math.floor(total / frameLen);
|
const numFrames = Math.floor(total / frameLen);
|
||||||
|
|
@ -96,7 +95,37 @@ export function getTableInfo(hapValue, tableUrls) {
|
||||||
return { transpose, tableUrl, index, midi, label };
|
return { transpose, tableUrl, index, midi, label };
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadBuffer = (url, ac, label) => {
|
// Extract the sample rate of a .wav file
|
||||||
|
function parseWavSampleRate(arrBuf) {
|
||||||
|
const dv = new DataView(arrBuf);
|
||||||
|
// Header is "RIFF<chunk size (4 bytes)>WAVE", so 12 bytes
|
||||||
|
let p = 12;
|
||||||
|
// Look through chunks for the format header
|
||||||
|
// (they will always have an 8 byte header (id and size) followed by a payload)
|
||||||
|
while (p + 8 <= dv.byteLength) {
|
||||||
|
// Parse id
|
||||||
|
const id = String.fromCharCode(dv.getUint8(p), dv.getUint8(p + 1), dv.getUint8(p + 2), dv.getUint8(p + 3));
|
||||||
|
// Parse chunk size
|
||||||
|
const size = dv.getUint32(p + 4, true);
|
||||||
|
if (id === 'fmt ') {
|
||||||
|
// The format chunk contains the sample rate after
|
||||||
|
// 8 bytes of header, 2 bytes of format tag, 2 bytes of num channels
|
||||||
|
// (for a total of 12)
|
||||||
|
return dv.getUint32(p + 12, true);
|
||||||
|
}
|
||||||
|
// Advance to next chunk
|
||||||
|
p += 8 + size + (size & 1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function decodeAtNativeRate(arr) {
|
||||||
|
const sr = parseWavSampleRate(arr) || 44100;
|
||||||
|
const tempAC = new OfflineAudioContext(1, 1, sr);
|
||||||
|
return await tempAC.decodeAudioData(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadBuffer = (url, label) => {
|
||||||
url = url.replace('#', '%23');
|
url = url.replace('#', '%23');
|
||||||
if (!loadCache[url]) {
|
if (!loadCache[url]) {
|
||||||
logger(`[wavetable] load table ${label}..`, 'load-table', { url });
|
logger(`[wavetable] load table ${label}..`, 'load-table', { url });
|
||||||
|
|
@ -107,7 +136,7 @@ const loadBuffer = (url, ac, label) => {
|
||||||
const took = Date.now() - timestamp;
|
const took = Date.now() - timestamp;
|
||||||
const size = humanFileSize(res.byteLength);
|
const size = humanFileSize(res.byteLength);
|
||||||
logger(`[wavetable] load table ${label}... done! loaded ${size} in ${took}ms`, 'loaded-table', { url });
|
logger(`[wavetable] load table ${label}... done! loaded ${size} in ${took}ms`, 'loaded-table', { url });
|
||||||
const decoded = await ac.decodeAudioData(res);
|
const decoded = await decodeAtNativeRate(res);
|
||||||
return decoded;
|
return decoded;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -127,25 +156,40 @@ function githubPath(base, subpath = '') {
|
||||||
return `https://raw.githubusercontent.com/${path}/${subpath}`;
|
return `https://raw.githubusercontent.com/${path}/${subpath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _processTables = (json, baseUrl, frameLen) => {
|
const _processTables = (json, baseUrl, frameLen, options = {}) => {
|
||||||
return Object.entries(json).forEach(([key, value]) => {
|
baseUrl = json._base || baseUrl;
|
||||||
if (typeof value === 'string') {
|
return Object.entries(json).forEach(([key, tables]) => {
|
||||||
value = [value];
|
if (key === '_base') return false;
|
||||||
|
if (typeof tables === 'string') {
|
||||||
|
tables = [tables];
|
||||||
}
|
}
|
||||||
if (typeof value !== 'object') {
|
if (typeof tables !== 'object') {
|
||||||
throw new Error('wrong json format for ' + key);
|
throw new Error('wrong json format for ' + key);
|
||||||
}
|
}
|
||||||
baseUrl = value._base || baseUrl;
|
let resolvedUrl = baseUrl;
|
||||||
if (baseUrl.startsWith('github:')) {
|
if (resolvedUrl.startsWith('github:')) {
|
||||||
baseUrl = githubPath(baseUrl, '');
|
resolvedUrl = githubPath(resolvedUrl, '');
|
||||||
|
}
|
||||||
|
tables = tables
|
||||||
|
.map((t) => resolvedUrl + t)
|
||||||
|
.filter((t) => {
|
||||||
|
if (!t.toLowerCase().endsWith('.wav')) {
|
||||||
|
logger(`[wavetable] skipping ${t} -- wavetables must be ".wav" format`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (tables.length) {
|
||||||
|
const { prebake, tag } = options;
|
||||||
|
registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, tables, frameLen), {
|
||||||
|
type: 'wavetable',
|
||||||
|
tables,
|
||||||
|
baseUrl,
|
||||||
|
frameLen,
|
||||||
|
prebake,
|
||||||
|
tag,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
value = value.map((v) => baseUrl + v);
|
|
||||||
registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, value, frameLen), {
|
|
||||||
type: 'wavetable',
|
|
||||||
tables: value,
|
|
||||||
baseUrl,
|
|
||||||
frameLen,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -154,7 +198,7 @@ const _processTables = (json, baseUrl, frameLen) => {
|
||||||
*
|
*
|
||||||
* @name tables
|
* @name tables
|
||||||
*/
|
*/
|
||||||
export const tables = async (url, frameLen, json) => {
|
export const tables = async (url, frameLen, json, options = {}) => {
|
||||||
if (json !== undefined) return _processTables(json, url, frameLen);
|
if (json !== undefined) return _processTables(json, url, frameLen);
|
||||||
if (url.startsWith('github:')) {
|
if (url.startsWith('github:')) {
|
||||||
url = githubPath(url, 'strudel.json');
|
url = githubPath(url, 'strudel.json');
|
||||||
|
|
@ -172,14 +216,14 @@ export const tables = async (url, frameLen, json) => {
|
||||||
}
|
}
|
||||||
return fetch(url)
|
return fetch(url)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((json) => _processTables(json, url, frameLen))
|
.then((json) => _processTables(json, url, frameLen, options))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw new Error(`error loading "${url}"`);
|
throw new Error(`error loading "${url}"`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async function onTriggerSynth(t, value, onended, bank, frameLen) {
|
async function onTriggerSynth(t, value, onended, tables, frameLen) {
|
||||||
const { s, n = 0, duration } = value;
|
const { s, n = 0, duration } = value;
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
|
const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
|
||||||
|
|
@ -188,7 +232,7 @@ async function onTriggerSynth(t, value, onended, bank, frameLen) {
|
||||||
wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE;
|
wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE;
|
||||||
}
|
}
|
||||||
const frequency = getFrequencyFromValue(value);
|
const frequency = getFrequencyFromValue(value);
|
||||||
const { tableUrl, label } = getTableInfo(value, bank);
|
const { tableUrl, label } = getTableInfo(value, tables);
|
||||||
const payload = await loadWavetableFrames(tableUrl, label, frameLen);
|
const payload = await loadWavetableFrames(tableUrl, label, frameLen);
|
||||||
const holdEnd = t + duration;
|
const holdEnd = t + duration;
|
||||||
const envEnd = holdEnd + release + 0.01;
|
const envEnd = holdEnd + release + 0.01;
|
||||||
|
|
|
||||||
|
|
@ -988,7 +988,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
{ name: 'warpMode', defaultValue: 0 },
|
{ name: 'warpMode', defaultValue: 0 },
|
||||||
{ name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 },
|
{ name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 },
|
||||||
{ name: 'spread', defaultValue: 0, minValue: 0, maxValue: 1 },
|
{ name: 'spread', defaultValue: 0, minValue: 0, maxValue: 1 },
|
||||||
{ name: 'phaserand', defaultValue: 1, minValue: 0, maxValue: 1 },
|
{ name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1155,10 +1155,10 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
_sampleFrame(frame, phase) {
|
_sampleFrame(frame, phase) {
|
||||||
const pos = phase * (frame.length - 1);
|
const pos = phase * frame.length;
|
||||||
const i = pos | 0;
|
const i = pos | 0;
|
||||||
const frac = pos - i;
|
const frac = pos - i;
|
||||||
const a = frame[i];
|
const a = frame[i % frame.length];
|
||||||
const b = frame[(i + 1) % frame.length];
|
const b = frame[(i + 1) % frame.length];
|
||||||
return a + (b - a) * frac;
|
return a + (b - a) * frac;
|
||||||
}
|
}
|
||||||
|
|
@ -1181,7 +1181,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
|
|
||||||
for (let i = 0; i < outL.length; i++) {
|
for (let i = 0; i < outL.length; i++) {
|
||||||
const detune = pv(parameters.detune, i);
|
const detune = pv(parameters.detune, i);
|
||||||
const spread = pv(parameters.spread, i) * 0.5 + 0.5;
|
|
||||||
const tablePos = pv(parameters.position, i);
|
const tablePos = pv(parameters.position, i);
|
||||||
const idx = tablePos * (this.numFrames - 1);
|
const idx = tablePos * (this.numFrames - 1);
|
||||||
const fIdx = idx | 0;
|
const fIdx = idx | 0;
|
||||||
|
|
@ -1189,11 +1188,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
const warpAmount = pv(parameters.warp, i);
|
const warpAmount = pv(parameters.warp, i);
|
||||||
const warpMode = pv(parameters.warpMode, i);
|
const warpMode = pv(parameters.warpMode, i);
|
||||||
const voices = pv(parameters.voices, i);
|
const voices = pv(parameters.voices, i);
|
||||||
|
const spread = voices > 1 ? pv(parameters.spread, i) : 0;
|
||||||
const phaseRand = pv(parameters.phaserand, i);
|
const phaseRand = pv(parameters.phaserand, i);
|
||||||
const gain1 = Math.sqrt(1 - spread);
|
const gain1 = Math.sqrt(0.5 - 0.5 * spread);
|
||||||
const gain2 = Math.sqrt(spread);
|
const gain2 = Math.sqrt(0.5 + 0.5 * spread);
|
||||||
let f = pv(parameters.frequency, i);
|
let f = pv(parameters.frequency, i);
|
||||||
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
||||||
|
const normalizer = 0.3 / Math.sqrt(voices);
|
||||||
for (let n = 0; n < voices; n++) {
|
for (let n = 0; n < voices; n++) {
|
||||||
const isOdd = (n & 1) == 1;
|
const isOdd = (n & 1) == 1;
|
||||||
let gainL = gain1;
|
let gainL = gain1;
|
||||||
|
|
@ -1206,19 +1207,19 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
|
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
|
||||||
const dPhase = fVoice / sampleRate;
|
const dPhase = fVoice / sampleRate;
|
||||||
const level = this._chooseMip(dPhase);
|
const level = this._chooseMip(dPhase);
|
||||||
const bank = this.tables[level];
|
const table = this.tables[level];
|
||||||
|
|
||||||
// warp phase then sample
|
// warp phase then sample
|
||||||
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
||||||
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||||
const s0 = this._sampleFrame(bank[fIdx], ph);
|
const s0 = this._sampleFrame(table[fIdx], ph);
|
||||||
const s1 = this._sampleFrame(bank[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
||||||
let s = s0 + (s1 - s0) * frac;
|
let s = s0 + (s1 - s0) * frac;
|
||||||
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||||
s = -s;
|
s = -s;
|
||||||
}
|
}
|
||||||
outL[i] += (s * gainL) / Math.sqrt(voices);
|
outL[i] += s * gainL * normalizer;
|
||||||
outR[i] += (s * gainR) / Math.sqrt(voices);
|
outR[i] += s * gainR * normalizer;
|
||||||
this.phase[n] = wrapPhase(this.phase[n] + dPhase);
|
this.phase[n] = wrapPhase(this.phase[n] + dPhase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11981,6 +11981,165 @@ exports[`runs examples > example "withValue" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "wtPos" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ (1/8 → 1/5) ⇝ 1/4 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ 1/8 ⇜ (1/5 → 1/4) | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ (3/8 → 2/5) ⇝ 1/2 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 3/8 ⇜ (2/5 → 1/2) | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ (1/2 → 3/5) ⇝ 5/8 | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ 1/2 ⇜ (3/5 → 5/8) | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ (3/4 → 4/5) ⇝ 7/8 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 3/4 ⇜ (4/5 → 7/8) | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ (9/8 → 6/5) ⇝ 5/4 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ 9/8 ⇜ (6/5 → 5/4) | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ (11/8 → 7/5) ⇝ 3/2 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 11/8 ⇜ (7/5 → 3/2) | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ (3/2 → 8/5) ⇝ 13/8 | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ 3/2 ⇜ (8/5 → 13/8) | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ (7/4 → 9/5) ⇝ 15/8 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 7/4 ⇜ (9/5 → 15/8) | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ (17/8 → 11/5) ⇝ 9/4 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ 17/8 ⇜ (11/5 → 9/4) | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ (19/8 → 12/5) ⇝ 5/2 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 19/8 ⇜ (12/5 → 5/2) | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ (5/2 → 13/5) ⇝ 21/8 | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ 5/2 ⇜ (13/5 → 21/8) | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ (11/4 → 14/5) ⇝ 23/8 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 11/4 ⇜ (14/5 → 23/8) | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ (25/8 → 16/5) ⇝ 13/4 | s:squelch note:F1 wtPos:0 ]",
|
||||||
|
"[ 25/8 ⇜ (16/5 → 13/4) | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ (27/8 → 17/5) ⇝ 7/2 | s:squelch note:F1 wtPos:0.25 ]",
|
||||||
|
"[ 27/8 ⇜ (17/5 → 7/2) | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ (7/2 → 18/5) ⇝ 29/8 | s:squelch note:F1 wtPos:0.5 ]",
|
||||||
|
"[ 7/2 ⇜ (18/5 → 29/8) | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ (15/4 → 19/5) ⇝ 31/8 | s:squelch note:F1 wtPos:0.75 ]",
|
||||||
|
"[ 15/4 ⇜ (19/5 → 31/8) | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:squelch note:F1 wtPos:1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "wtWarp" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ (1/8 → 1/5) ⇝ 1/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ 1/8 ⇜ (1/5 → 1/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 1/4 → 3/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ (3/8 → 2/5) ⇝ 1/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 3/8 ⇜ (2/5 → 1/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ (1/2 → 3/5) ⇝ 5/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ 1/2 ⇜ (3/5 → 5/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 5/8 → 3/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ (3/4 → 4/5) ⇝ 7/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 3/4 ⇜ (4/5 → 7/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
"[ 7/8 → 1/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
"[ 1/1 → 9/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ (9/8 → 6/5) ⇝ 5/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ 9/8 ⇜ (6/5 → 5/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 5/4 → 11/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ (11/8 → 7/5) ⇝ 3/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 11/8 ⇜ (7/5 → 3/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ (3/2 → 8/5) ⇝ 13/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ 3/2 ⇜ (8/5 → 13/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 13/8 → 7/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ (7/4 → 9/5) ⇝ 15/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 7/4 ⇜ (9/5 → 15/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
"[ 15/8 → 2/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
"[ 2/1 → 17/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ (17/8 → 11/5) ⇝ 9/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ 17/8 ⇜ (11/5 → 9/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 9/4 → 19/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ (19/8 → 12/5) ⇝ 5/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 19/8 ⇜ (12/5 → 5/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ (5/2 → 13/5) ⇝ 21/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ 5/2 ⇜ (13/5 → 21/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 21/8 → 11/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ (11/4 → 14/5) ⇝ 23/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 11/4 ⇜ (14/5 → 23/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
"[ 23/8 → 3/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
"[ 3/1 → 25/8 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ (25/8 → 16/5) ⇝ 13/4 | s:basique note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ 25/8 ⇜ (16/5 → 13/4) | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 13/4 → 27/8 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ (27/8 → 17/5) ⇝ 7/2 | s:basique note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 27/8 ⇜ (17/5 → 7/2) | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ (7/2 → 18/5) ⇝ 29/8 | s:basique note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ 7/2 ⇜ (18/5 → 29/8) | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 29/8 → 15/4 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ (15/4 → 19/5) ⇝ 31/8 | s:basique note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||||
|
"[ 15/4 ⇜ (19/5 → 31/8) | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
"[ 31/8 → 4/1 | s:basique note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "wtWarpMode" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:asym ]",
|
||||||
|
"[ (1/8 → 1/5) ⇝ 1/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:asym ]",
|
||||||
|
"[ 1/8 ⇜ (1/5 → 1/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]",
|
||||||
|
"[ 1/4 → 3/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]",
|
||||||
|
"[ (3/8 → 2/5) ⇝ 1/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:asym ]",
|
||||||
|
"[ 3/8 ⇜ (2/5 → 1/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:asym ]",
|
||||||
|
"[ (1/2 → 3/5) ⇝ 5/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:bendp ]",
|
||||||
|
"[ 1/2 ⇜ (3/5 → 5/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]",
|
||||||
|
"[ 5/8 → 3/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]",
|
||||||
|
"[ (3/4 → 4/5) ⇝ 7/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:bendp ]",
|
||||||
|
"[ 3/4 ⇜ (4/5 → 7/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:bendp ]",
|
||||||
|
"[ 7/8 → 1/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:bendp ]",
|
||||||
|
"[ 1/1 → 9/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ (9/8 → 6/5) ⇝ 5/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||||
|
"[ 9/8 ⇜ (6/5 → 5/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 5/4 → 11/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ (11/8 → 7/5) ⇝ 3/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||||
|
"[ 11/8 ⇜ (7/5 → 3/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||||
|
"[ (3/2 → 8/5) ⇝ 13/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:logistic ]",
|
||||||
|
"[ 3/2 ⇜ (8/5 → 13/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]",
|
||||||
|
"[ 13/8 → 7/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]",
|
||||||
|
"[ (7/4 → 9/5) ⇝ 15/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:logistic ]",
|
||||||
|
"[ 7/4 ⇜ (9/5 → 15/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:logistic ]",
|
||||||
|
"[ 15/8 → 2/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:logistic ]",
|
||||||
|
"[ 2/1 → 17/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:sync ]",
|
||||||
|
"[ (17/8 → 11/5) ⇝ 9/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:sync ]",
|
||||||
|
"[ 17/8 ⇜ (11/5 → 9/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]",
|
||||||
|
"[ 9/4 → 19/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]",
|
||||||
|
"[ (19/8 → 12/5) ⇝ 5/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:sync ]",
|
||||||
|
"[ 19/8 ⇜ (12/5 → 5/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:sync ]",
|
||||||
|
"[ (5/2 → 13/5) ⇝ 21/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:wormhole ]",
|
||||||
|
"[ 5/2 ⇜ (13/5 → 21/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]",
|
||||||
|
"[ 21/8 → 11/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]",
|
||||||
|
"[ (11/4 → 14/5) ⇝ 23/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:wormhole ]",
|
||||||
|
"[ 11/4 ⇜ (14/5 → 23/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:wormhole ]",
|
||||||
|
"[ 23/8 → 3/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:wormhole ]",
|
||||||
|
"[ 3/1 → 25/8 | s:morgana note:F1 wtWarp:0 wtWarpMode:brownian ]",
|
||||||
|
"[ (25/8 → 16/5) ⇝ 13/4 | s:morgana note:F1 wtWarp:0 wtWarpMode:brownian ]",
|
||||||
|
"[ 25/8 ⇜ (16/5 → 13/4) | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]",
|
||||||
|
"[ 13/4 → 27/8 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]",
|
||||||
|
"[ (27/8 → 17/5) ⇝ 7/2 | s:morgana note:F1 wtWarp:0.25 wtWarpMode:brownian ]",
|
||||||
|
"[ 27/8 ⇜ (17/5 → 7/2) | s:morgana note:F1 wtWarp:0.5 wtWarpMode:brownian ]",
|
||||||
|
"[ (7/2 → 18/5) ⇝ 29/8 | s:morgana note:F1 wtWarp:0.5 wtWarpMode:asym ]",
|
||||||
|
"[ 7/2 ⇜ (18/5 → 29/8) | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]",
|
||||||
|
"[ 29/8 → 15/4 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]",
|
||||||
|
"[ (15/4 → 19/5) ⇝ 31/8 | s:morgana note:F1 wtWarp:0.75 wtWarpMode:asym ]",
|
||||||
|
"[ 15/4 ⇜ (19/5 → 31/8) | s:morgana note:F1 wtWarp:1 wtWarpMode:asym ]",
|
||||||
|
"[ 31/8 → 4/1 | s:morgana note:F1 wtWarp:1 wtWarpMode:asym ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "xfade" example index 0 1`] = `
|
exports[`runs examples > example "xfade" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | s:hh gain:0 ]",
|
"[ 0/1 → 1/8 | s:hh gain:0 ]",
|
||||||
|
|
|
||||||
54
website/public/uzu-wavetables.json
Normal file
54
website/public/uzu-wavetables.json
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"_base": "http://localhost:5432",
|
||||||
|
"Bad Day": [
|
||||||
|
"/Bad Day.wav"
|
||||||
|
],
|
||||||
|
"Basique": [
|
||||||
|
"/Basique.wav"
|
||||||
|
],
|
||||||
|
"Crickets": [
|
||||||
|
"/Crickets.wav"
|
||||||
|
],
|
||||||
|
"Curses": [
|
||||||
|
"/Curses.wav"
|
||||||
|
],
|
||||||
|
"Earl Grey": [
|
||||||
|
"/Earl Grey.wav"
|
||||||
|
],
|
||||||
|
"Echoes": [
|
||||||
|
"/Echoes.wav"
|
||||||
|
],
|
||||||
|
"Glimmer": [
|
||||||
|
"/Glimmer.wav"
|
||||||
|
],
|
||||||
|
"Majick": [
|
||||||
|
"/Majick.wav"
|
||||||
|
],
|
||||||
|
"Meditation": [
|
||||||
|
"/Meditation.wav"
|
||||||
|
],
|
||||||
|
"Morgana": [
|
||||||
|
"/Morgana.wav"
|
||||||
|
],
|
||||||
|
"Red Alert": [
|
||||||
|
"/Red Alert.wav"
|
||||||
|
],
|
||||||
|
"Sad Piano": [
|
||||||
|
"/Sad Piano.wav"
|
||||||
|
],
|
||||||
|
"Shook": [
|
||||||
|
"/Shook.wav"
|
||||||
|
],
|
||||||
|
"Sludge": [
|
||||||
|
"/Sludge.wav"
|
||||||
|
],
|
||||||
|
"Squelch": [
|
||||||
|
"/Squelch.wav"
|
||||||
|
],
|
||||||
|
"Summers Day": [
|
||||||
|
"/Summers Day.wav"
|
||||||
|
],
|
||||||
|
"Wasp": [
|
||||||
|
"/Wasp.wav"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,9 @@ export function SoundsTab() {
|
||||||
if (soundsFilter === soundFilterType.SYNTHS) {
|
if (soundsFilter === soundFilterType.SYNTHS) {
|
||||||
return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type));
|
return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type));
|
||||||
}
|
}
|
||||||
|
if (soundsFilter === soundFilterType.WAVETABLES) {
|
||||||
|
return filtered.filter(([_, { data }]) => data.type === 'wavetable');
|
||||||
|
}
|
||||||
//TODO: tidy this up, it does not need to be saved in settings
|
//TODO: tidy this up, it does not need to be saved in settings
|
||||||
if (soundsFilter === 'importSounds') {
|
if (soundsFilter === 'importSounds') {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -74,6 +77,7 @@ export function SoundsTab() {
|
||||||
samples: 'samples',
|
samples: 'samples',
|
||||||
drums: 'drum-machines',
|
drums: 'drum-machines',
|
||||||
synths: 'Synths',
|
synths: 'Synths',
|
||||||
|
wavetables: 'Wavetables',
|
||||||
user: 'User',
|
user: 'User',
|
||||||
importSounds: 'import-sounds',
|
importSounds: 'import-sounds',
|
||||||
}}
|
}}
|
||||||
|
|
@ -125,7 +129,7 @@ export function SoundsTab() {
|
||||||
>
|
>
|
||||||
{' '}
|
{' '}
|
||||||
{name}
|
{name}
|
||||||
{data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''}
|
{data?.type === 'sample' || data?.type === 'wavetable' ? `(${getSamples(data.samples)})` : ''}
|
||||||
{data?.type === 'soundfont' ? `(${data.fonts.length})` : ''}
|
{data?.type === 'soundfont' ? `(${data.fonts.length})` : ''}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Pattern, noteToMidi, valueToMidi } from '@strudel/core';
|
import { Pattern, noteToMidi, valueToMidi } from '@strudel/core';
|
||||||
import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio';
|
import { aliasBank, registerSynthSounds, registerZZFXSounds, samples, tables } from '@strudel/webaudio';
|
||||||
import { registerSamplesFromDB } from './idbutils.mjs';
|
import { registerSamplesFromDB } from './idbutils.mjs';
|
||||||
import './piano.mjs';
|
import './piano.mjs';
|
||||||
import './files.mjs';
|
import './files.mjs';
|
||||||
|
|
@ -32,6 +32,10 @@ export async function prebake() {
|
||||||
prebake: true,
|
prebake: true,
|
||||||
tag: 'drum-machines',
|
tag: 'drum-machines',
|
||||||
}),
|
}),
|
||||||
|
tables(`${baseNoTrailing}/uzu-wavetables.json`, 2048, undefined, {
|
||||||
|
prebake: true,
|
||||||
|
tag: 'wavetables',
|
||||||
|
}),
|
||||||
samples(`${baseNoTrailing}/mridangam.json`, undefined, { prebake: true, tag: 'drum-machines' }),
|
samples(`${baseNoTrailing}/mridangam.json`, undefined, { prebake: true, tag: 'drum-machines' }),
|
||||||
samples(
|
samples(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export const soundFilterType = {
|
||||||
DRUMS: 'drums',
|
DRUMS: 'drums',
|
||||||
SAMPLES: 'samples',
|
SAMPLES: 'samples',
|
||||||
SYNTHS: 'synths',
|
SYNTHS: 'synths',
|
||||||
|
WAVETABLES: 'wavetables',
|
||||||
ALL: 'all',
|
ALL: 'all',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue