mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 14:26:58 -04:00
Merge pull request '<Warn: Don't merge until tables uploaded> Feature: Wavetable synth improvements' (#1607) from glossing/strudel:glossing/wavetable-synth into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1607
This commit is contained in:
+179
-3
@@ -93,22 +93,193 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound');
|
||||
* @name wtPos
|
||||
* @param {number | Pattern} position Position in the wavetable from 0 to 1
|
||||
* @synonyms wavetablePosition
|
||||
*
|
||||
* @example
|
||||
* s("squelch").bank("wt_digital").seg(8).note("F1").wtPos("0 0.25 0.5 0.75 1")
|
||||
*/
|
||||
export const { wtPos, wavetablePosition } = registerControl('wtPos', 'wavetablePosition');
|
||||
|
||||
/**
|
||||
* Attack time of the wavetable oscillator's position envelope
|
||||
*
|
||||
* @name wtPosAttack
|
||||
* @synonyms wtPosAtt
|
||||
* @param {number | Pattern} time attack time in seconds
|
||||
*/
|
||||
export const { wtPosAttack, wtPosAtt } = registerControl('wtPosAttack', 'wtPosAtt');
|
||||
|
||||
/**
|
||||
* Decay time of the wavetable oscillator's position envelope
|
||||
*
|
||||
* @name wtPosDecay
|
||||
* @synonyms wtPosDec
|
||||
* @param {number | Pattern} time decay time in seconds
|
||||
*/
|
||||
export const { wtPosDecay, wtPosDec } = registerControl('wtPosDecay', 'wtPosDec');
|
||||
|
||||
/**
|
||||
* Sustain time of the wavetable oscillator's position envelope
|
||||
*
|
||||
* @name wtPosAttack
|
||||
* @synonyms wtPosSus
|
||||
* @param {number | Pattern} gain sustain level (0 to 1)
|
||||
*/
|
||||
export const { wtPosSustain, wtPosSus } = registerControl('wtPosSustain', 'wtPosSus');
|
||||
|
||||
/**
|
||||
* Release time of the wavetable oscillator's position envelope
|
||||
*
|
||||
* @name wtPosRelease
|
||||
* @synonyms wtPosRel
|
||||
* @param {number | Pattern} time release time in seconds
|
||||
*/
|
||||
export const { wtPosRelease, wtPosRel } = registerControl('wtPosRelease', 'wtPosRel');
|
||||
|
||||
/**
|
||||
* Rate of the LFO for the wavetable oscillator's position
|
||||
*
|
||||
* @name wtPosRate
|
||||
* @param {number | Pattern} rate rate in hertz
|
||||
*/
|
||||
export const { wtPosRate } = registerControl('wtPosRate');
|
||||
|
||||
/**
|
||||
* Depth of the LFO for the wavetable oscillator's position
|
||||
*
|
||||
* @name wtPosDepth
|
||||
* @param {number | Pattern} depth depth of modulation
|
||||
*/
|
||||
export const { wtPosDepth } = registerControl('wtPosDepth');
|
||||
|
||||
/**
|
||||
* Whether to sync the LFO for the wavetable oscillator's position
|
||||
*
|
||||
* @name wtPosSynced
|
||||
* @param {number | Pattern} synced Whether to sync the lfo to CPM. > 0.5 will be synced
|
||||
*/
|
||||
export const { wtPosSynced } = registerControl('wtPosSynced');
|
||||
|
||||
/**
|
||||
* Shape of the LFO for the wavetable oscillator's position
|
||||
*
|
||||
* @name wtPosShape
|
||||
* @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
|
||||
*/
|
||||
export const { wtPosShape } = registerControl('wtPosShape');
|
||||
|
||||
/**
|
||||
* DC offset of the LFO for the wavetable oscillator's position
|
||||
*
|
||||
* @name wtPosDCOffset
|
||||
* @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
|
||||
*/
|
||||
export const { wtPosDCOffset } = registerControl('wtPosDCOffset');
|
||||
|
||||
/**
|
||||
* Skew of the LFO for the wavetable oscillator's position
|
||||
*
|
||||
* @name wtPosSkew
|
||||
* @param {number | Pattern} skew How much to bend the LFO shape
|
||||
*/
|
||||
export const { wtPosSkew } = registerControl('wtPosSkew');
|
||||
|
||||
/**
|
||||
* Amount of warp (alteration of the waveform) to apply to the wavetable oscillator
|
||||
*
|
||||
* @name wtWarp
|
||||
* @param {number | Pattern} amount Warp of the wavetable from 0 to 1
|
||||
* @synonyms wavetableWarp
|
||||
*
|
||||
* @example
|
||||
* s("basique").bank("wt_digital").seg(8).note("F1").wtWarp("0 0.25 0.5 0.75 1")
|
||||
* .wtWarpMode("spin")
|
||||
*/
|
||||
export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWarp');
|
||||
|
||||
/**
|
||||
* Amount of warp (alteration of the waveform) to apply to the wavetable oscillator.
|
||||
* Attack time of the wavetable oscillator's warp envelope
|
||||
*
|
||||
* @name wtWarpAttack
|
||||
* @synonyms wtWarpAtt
|
||||
* @param {number | Pattern} time attack time in seconds
|
||||
*/
|
||||
export const { wtWarpAttack, wtWarpAtt } = registerControl('wtWarpAttack', 'wtWarpAtt');
|
||||
|
||||
/**
|
||||
* Decay time of the wavetable oscillator's warp envelope
|
||||
*
|
||||
* @name wtWarpDecay
|
||||
* @synonyms wtWarpDec
|
||||
* @param {number | Pattern} time decay time in seconds
|
||||
*/
|
||||
export const { wtWarpDecay, wtWarpDec } = registerControl('wtWarpDecay', 'wtWarpDec');
|
||||
|
||||
/**
|
||||
* Sustain time of the wavetable oscillator's warp envelope
|
||||
*
|
||||
* @name wtWarpAttack
|
||||
* @synonyms wtWarpSus
|
||||
* @param {number | Pattern} gain sustain level (0 to 1)
|
||||
*/
|
||||
export const { wtWarpSustain, wtWarpSus } = registerControl('wtWarpSustain', 'wtWarpSus');
|
||||
|
||||
/**
|
||||
* Release time of the wavetable oscillator's warp envelope
|
||||
*
|
||||
* @name wtWarpRelease
|
||||
* @synonyms wtWarpRel
|
||||
* @param {number | Pattern} time release time in seconds
|
||||
*/
|
||||
export const { wtWarpRelease, wtWarpRel } = registerControl('wtWarpRelease', 'wtWarpRel');
|
||||
|
||||
/**
|
||||
* Rate of the LFO for the wavetable oscillator's warp
|
||||
*
|
||||
* @name wtWarpRate
|
||||
* @param {number | Pattern} rate rate in hertz
|
||||
*/
|
||||
export const { wtWarpRate } = registerControl('wtWarpRate');
|
||||
|
||||
/**
|
||||
* Depth of the LFO for the wavetable oscillator's warp
|
||||
*
|
||||
* @name wtWarpDepth
|
||||
* @param {number | Pattern} depth depth of modulation
|
||||
*/
|
||||
export const { wtWarpDepth } = registerControl('wtWarpDepth');
|
||||
|
||||
/**
|
||||
* Whether to sync the LFO for the wavetable oscillator's warp
|
||||
*
|
||||
* @name wtWarpSynced
|
||||
* @param {number | Pattern} synced Whether to sync the lfo to CPM. > 0.5 will be synced
|
||||
*/
|
||||
export const { wtWarpSynced } = registerControl('wtWarpSynced');
|
||||
|
||||
/**
|
||||
* Shape of the LFO for the wavetable oscillator's warp
|
||||
*
|
||||
* @name wtWarpShape
|
||||
* @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
|
||||
*/
|
||||
export const { wtWarpShape } = registerControl('wtWarpShape');
|
||||
|
||||
/**
|
||||
* DC offset of the LFO for the wavetable oscillator's warp
|
||||
*
|
||||
* @name wtWarpDCOffset
|
||||
* @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
|
||||
*/
|
||||
export const { wtWarpDCOffset } = registerControl('wtWarpDCOffset');
|
||||
|
||||
/**
|
||||
* Skew of the LFO for the wavetable oscillator's warp
|
||||
*
|
||||
* @name wtWarpSkew
|
||||
* @param {number | Pattern} skew How much to bend the LFO shape
|
||||
*/
|
||||
export const { wtWarpSkew } = registerControl('wtWarpSkew');
|
||||
|
||||
/**
|
||||
* Type of warp (alteration of the waveform) to apply to the wavetable oscillator.
|
||||
*
|
||||
* The current options are: none, asym, bendp, bendm, bendmp, sync, quant, fold, pwm, orbit,
|
||||
* spin, chaos, primes, binary, brownian, reciprocal, wormhole, logistic, sigmoid, fractal, flip
|
||||
@@ -116,6 +287,9 @@ export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWar
|
||||
* @name wtWarpMode
|
||||
* @param {number | string | Pattern} mode Warp mode
|
||||
* @synonyms wavetableWarpMode
|
||||
* @example
|
||||
* s("morgana").bank("wt_digital").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');
|
||||
@@ -126,6 +300,8 @@ export const { wtWarpMode, wavetableWarpMode } = registerControl('wtWarpMode', '
|
||||
* @name wtPhaseRand
|
||||
* @param {number | Pattern} amount Randomness of the initial phase. Between 0 (not random) and 1 (fully random)
|
||||
* @synonyms wavetablePhaseRand
|
||||
* @example
|
||||
* s("basique").bank("wt_digital").seg(16).wtPhaseRand("<0 1>")
|
||||
*
|
||||
*/
|
||||
export const { wtPhaseRand, wavetablePhaseRand } = registerControl('wtPhaseRand', 'wavetablePhaseRand');
|
||||
|
||||
@@ -97,6 +97,35 @@ export const getParamADSR = (
|
||||
param[ramp](min, end + release);
|
||||
};
|
||||
|
||||
function getModulationShapeInput(val) {
|
||||
if (typeof val === 'number') {
|
||||
return val % 5;
|
||||
}
|
||||
return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0;
|
||||
}
|
||||
|
||||
export function getLfo(audioContext, begin, end, properties = {}) {
|
||||
const { shape = 0, ...props } = properties;
|
||||
const { dcoffset = -0.5, depth = 1 } = properties;
|
||||
const lfoprops = {
|
||||
frequency: 1,
|
||||
depth,
|
||||
skew: 0.5,
|
||||
phaseoffset: 0,
|
||||
time: begin,
|
||||
begin,
|
||||
end,
|
||||
shape: getModulationShapeInput(shape),
|
||||
dcoffset,
|
||||
min: dcoffset * depth,
|
||||
max: dcoffset * depth + depth,
|
||||
curve: 1,
|
||||
...props,
|
||||
};
|
||||
|
||||
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
||||
}
|
||||
|
||||
export function getCompressor(ac, threshold, ratio, knee, attack, release) {
|
||||
const options = {
|
||||
threshold: threshold ?? -3,
|
||||
|
||||
@@ -9,7 +9,7 @@ import './reverb.mjs';
|
||||
import './vowel.mjs';
|
||||
import { nanFallback, _mod, cycleToSeconds } from './util.mjs';
|
||||
import workletsUrl from './worklets.mjs?audioworklet';
|
||||
import { createFilter, gainNode, getCompressor, getWorklet, effectSend } from './helpers.mjs';
|
||||
import { createFilter, gainNode, getCompressor, getLfo, getWorklet, effectSend } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
import { loadBuffer } from './sampler.mjs';
|
||||
@@ -29,13 +29,6 @@ export function setMultiChannelOrbits(bool) {
|
||||
multiChannelOrbits = bool == true;
|
||||
}
|
||||
|
||||
function getModulationShapeInput(val) {
|
||||
if (typeof val === 'number') {
|
||||
return val % 5;
|
||||
}
|
||||
return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0;
|
||||
}
|
||||
|
||||
export const soundMap = map();
|
||||
|
||||
export function registerSound(key, onTrigger, data = {}) {
|
||||
@@ -314,28 +307,6 @@ export function connectToDestination(input, channels) {
|
||||
controller.output.connectToDestination(input, channels);
|
||||
}
|
||||
|
||||
export function getLfo(audioContext, begin, end, properties = {}) {
|
||||
const { shape = 0, ...props } = properties;
|
||||
const { dcoffset = -0.5, depth = 1 } = properties;
|
||||
const lfoprops = {
|
||||
frequency: 1,
|
||||
depth,
|
||||
skew: 0.5,
|
||||
phaseoffset: 0,
|
||||
time: begin,
|
||||
begin,
|
||||
end,
|
||||
shape: getModulationShapeInput(shape),
|
||||
dcoffset,
|
||||
min: dcoffset * depth,
|
||||
max: dcoffset * depth + depth,
|
||||
curve: 1,
|
||||
...props,
|
||||
};
|
||||
|
||||
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
||||
}
|
||||
|
||||
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
|
||||
const ac = getAudioContext();
|
||||
const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
||||
@@ -682,6 +653,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||
tremolo = cps * tremolosync;
|
||||
}
|
||||
|
||||
if (value.wtPosSynced != null) {
|
||||
value.wtPosRate /= cps;
|
||||
}
|
||||
|
||||
if (value.wtWarpSynced != null) {
|
||||
value.wtWarpRate /= cps;
|
||||
}
|
||||
|
||||
if (tremolo !== undefined) {
|
||||
// Allow clipping of modulator for more dynamic possiblities, and to prevent speaker overload
|
||||
// EX: a triangle waveform will clip like this /-\ when the depth is above 1
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { clamp } from './util.mjs';
|
||||
import { registerSound, getAudioContext, soundMap, getLfo } from './superdough.mjs';
|
||||
import { registerSound, getAudioContext, soundMap } from './superdough.mjs';
|
||||
import {
|
||||
applyFM,
|
||||
destroyAudioWorkletNode,
|
||||
gainNode,
|
||||
getADSRValues,
|
||||
getFrequencyFromValue,
|
||||
getLfo,
|
||||
getParamADSR,
|
||||
getPitchEnvelope,
|
||||
getVibratoOscillator,
|
||||
|
||||
@@ -76,6 +76,7 @@ export function cycleToSeconds(cycle, cps) {
|
||||
export function secondsToCycle(t, cps) {
|
||||
return t * cps;
|
||||
}
|
||||
|
||||
// deduces relevant info for sample loading from hap.value and sample definition
|
||||
// it encapsulates the core sampler logic into a pure and synchronous function
|
||||
// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { getAudioContext, registerSound } from './index.mjs';
|
||||
import { getCommonSampleInfo, getSoundIndex, valueToMidi } from './util.mjs';
|
||||
import { getCommonSampleInfo } from './util.mjs';
|
||||
import {
|
||||
destroyAudioWorkletNode,
|
||||
getADSRValues,
|
||||
getFrequencyFromValue,
|
||||
getLfo,
|
||||
getParamADSR,
|
||||
getPitchEnvelope,
|
||||
getVibratoOscillator,
|
||||
@@ -39,8 +40,7 @@ export const WarpMode = Object.freeze({
|
||||
});
|
||||
|
||||
async function loadWavetableFrames(url, label, frameLen = 2048) {
|
||||
const ac = getAudioContext();
|
||||
const buf = await loadBuffer(url, ac, label);
|
||||
const buf = await loadBuffer(url, label);
|
||||
const ch0 = buf.getChannelData(0);
|
||||
const total = ch0.length;
|
||||
const numFrames = Math.max(1, Math.floor(total / frameLen));
|
||||
@@ -86,11 +86,37 @@ function humanFileSize(bytes, si) {
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
|
||||
export function getTableInfo(hapValue, urls) {
|
||||
return getCommonSampleInfo(hapValue, urls);
|
||||
// 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;
|
||||
}
|
||||
|
||||
const loadBuffer = (url, ac, label) => {
|
||||
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');
|
||||
if (!loadCache[url]) {
|
||||
logger(`[wavetable] load table ${label}..`, 'load-table', { url });
|
||||
@@ -101,7 +127,7 @@ const loadBuffer = (url, ac, label) => {
|
||||
const took = Date.now() - timestamp;
|
||||
const size = humanFileSize(res.byteLength);
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -121,27 +147,39 @@ function githubPath(base, subpath = '') {
|
||||
return `https://raw.githubusercontent.com/${path}/${subpath}`;
|
||||
}
|
||||
|
||||
const _processTables = (json, baseUrl, frameLen) => {
|
||||
return Object.entries(json).forEach(([key, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
value = [value];
|
||||
const _processTables = (json, baseUrl, frameLen, options = {}) => {
|
||||
baseUrl = json._base || baseUrl;
|
||||
return Object.entries(json).forEach(([key, tables]) => {
|
||||
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);
|
||||
}
|
||||
baseUrl = value._base || baseUrl;
|
||||
if (baseUrl.startsWith('github:')) {
|
||||
baseUrl = githubPath(baseUrl, '');
|
||||
let resolvedUrl = baseUrl;
|
||||
if (resolvedUrl.startsWith('github:')) {
|
||||
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) {
|
||||
registerWaveTable(key, tables, { baseUrl, frameLen });
|
||||
}
|
||||
value = value.map((v) => baseUrl + v);
|
||||
registerWaveTable(key, value, { baseUrl, frameLen });
|
||||
});
|
||||
};
|
||||
|
||||
export function registerWaveTable(key, bank, params) {
|
||||
registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, bank, params?.frameLen ?? 2048), {
|
||||
export function registerWaveTable(key, tables, params) {
|
||||
registerSound(key, (t, hapValue, onended) => onTriggerSynth(t, hapValue, onended, tables, params?.frameLen ?? 2048), {
|
||||
type: 'wavetable',
|
||||
tables: bank,
|
||||
tables,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
@@ -151,7 +189,7 @@ export function registerWaveTable(key, bank, params) {
|
||||
*
|
||||
* @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 (url.startsWith('github:')) {
|
||||
url = githubPath(url, 'strudel.json');
|
||||
@@ -169,14 +207,14 @@ export const tables = async (url, frameLen, json) => {
|
||||
}
|
||||
return fetch(url)
|
||||
.then((res) => res.json())
|
||||
.then((json) => _processTables(json, url, frameLen))
|
||||
.then((json) => _processTables(json, url, frameLen, options))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
throw new Error(`error loading "${url}"`);
|
||||
});
|
||||
};
|
||||
|
||||
export async function onTriggerSynth(t, value, onended, bank, frameLen) {
|
||||
export async function onTriggerSynth(t, value, onended, tables, frameLen) {
|
||||
const { s, n = 0, duration } = value;
|
||||
const ac = getAudioContext();
|
||||
const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
|
||||
@@ -185,10 +223,11 @@ export async function onTriggerSynth(t, value, onended, bank, frameLen) {
|
||||
wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE;
|
||||
}
|
||||
const frequency = getFrequencyFromValue(value);
|
||||
const { url, label } = getTableInfo(value, bank);
|
||||
const { url, label } = getCommonSampleInfo(value, tables);
|
||||
const payload = await loadWavetableFrames(url, label, frameLen);
|
||||
const holdEnd = t + duration;
|
||||
const envEnd = holdEnd + release + 0.01;
|
||||
const endWithRelease = holdEnd + release;
|
||||
const envEnd = endWithRelease + 0.01;
|
||||
const source = getWorklet(
|
||||
ac,
|
||||
'wavetable-oscillator-processor',
|
||||
@@ -211,6 +250,39 @@ export async function onTriggerSynth(t, value, onended, bank, frameLen) {
|
||||
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
||||
return;
|
||||
}
|
||||
const posADSRParams = [value.wtPosAttack, value.wtPosDecay, value.wtPosSustain, value.wtPosRelease];
|
||||
const warpADSRParams = [value.wtPosAttack, value.wtPosDecay, value.wtPosSustain, value.wtPosRelease];
|
||||
const wtParams = source.parameters;
|
||||
const positionParam = wtParams.get('position');
|
||||
const warpParam = wtParams.get('warp');
|
||||
let posLFO;
|
||||
if (posADSRParams.some((p) => p !== undefined)) {
|
||||
const [pAttack, pDecay, pSustain, pRelease] = getADSRValues(posADSRParams);
|
||||
getParamADSR(positionParam, pAttack, pDecay, pSustain, pRelease, 0, 1, t, holdEnd, 'linear');
|
||||
} else {
|
||||
const posLFO = getLfo(ac, t, endWithRelease, {
|
||||
frequency: value.wtPosRate,
|
||||
depth: value.wtPosDepth,
|
||||
shape: value.wtPosShape,
|
||||
skew: value.wtPosSkew,
|
||||
dcoffset: value.wtPosDCOffset ?? 0,
|
||||
});
|
||||
posLFO.connect(positionParam);
|
||||
}
|
||||
let warpLFO;
|
||||
if (posADSRParams.some((p) => p !== undefined)) {
|
||||
const [wAttack, wDecay, wSustain, wRelease] = getADSRValues(warpADSRParams);
|
||||
getParamADSR(warpParam, wAttack, wDecay, wSustain, wRelease, 0, 1, t, holdEnd, 'linear');
|
||||
} else {
|
||||
const warpLFO = getLfo(ac, t, endWithRelease, {
|
||||
frequency: value.wtWarpRate,
|
||||
depth: value.wtWarpDepth,
|
||||
shape: value.wtWarpShape,
|
||||
skew: value.wtWarpSkew,
|
||||
dcoffset: value.wtWarpDCOffset ?? 0,
|
||||
});
|
||||
warpLFO.connect(warpParam);
|
||||
}
|
||||
const vibratoOscillator = getVibratoOscillator(source.detune, value, t);
|
||||
const envGain = ac.createGain();
|
||||
const node = source.connect(envGain);
|
||||
@@ -224,6 +296,8 @@ export async function onTriggerSynth(t, value, onended, bank, frameLen) {
|
||||
destroyAudioWorkletNode(source);
|
||||
vibratoOscillator?.stop();
|
||||
node.disconnect();
|
||||
posLFO?.disconnect();
|
||||
warpLFO?.disconnect();
|
||||
onended();
|
||||
},
|
||||
t,
|
||||
|
||||
@@ -1054,8 +1054,8 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
{ name: 'warp', defaultValue: 0, minValue: 0, maxValue: 1 },
|
||||
{ name: 'warpMode', defaultValue: 0 },
|
||||
{ name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 },
|
||||
{ name: 'spread', defaultValue: 0, minValue: 0, maxValue: 1 },
|
||||
{ name: 'phaserand', defaultValue: 1, minValue: 0, maxValue: 1 },
|
||||
{ name: 'spread', defaultValue: 0.18, minValue: 0, maxValue: 1 },
|
||||
{ name: 'phaserand', defaultValue: 0, minValue: 0, maxValue: 1 },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1222,10 +1222,10 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
|
||||
_sampleFrame(frame, phase) {
|
||||
const pos = phase * (frame.length - 1);
|
||||
const pos = phase * frame.length;
|
||||
const i = pos | 0;
|
||||
const frac = pos - i;
|
||||
const a = frame[i];
|
||||
const a = frame[i % frame.length];
|
||||
const b = frame[(i + 1) % frame.length];
|
||||
return a + (b - a) * frac;
|
||||
}
|
||||
@@ -1249,7 +1249,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
|
||||
for (let i = 0; i < outL.length; i++) {
|
||||
const detune = pv(parameters.detune, i);
|
||||
const spread = pv(parameters.spread, i) * 0.5 + 0.5;
|
||||
const tablePos = pv(parameters.position, i);
|
||||
const idx = tablePos * (this.numFrames - 1);
|
||||
const fIdx = idx | 0;
|
||||
@@ -1257,11 +1256,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
const warpAmount = pv(parameters.warp, i);
|
||||
const warpMode = pv(parameters.warpMode, i);
|
||||
const voices = pv(parameters.voices, i);
|
||||
const spread = voices > 1 ? pv(parameters.spread, i) : 0;
|
||||
const phaseRand = pv(parameters.phaserand, i);
|
||||
const gain1 = Math.sqrt(1 - spread);
|
||||
const gain2 = Math.sqrt(spread);
|
||||
const gain1 = Math.sqrt(0.5 - 0.5 * spread);
|
||||
const gain2 = Math.sqrt(0.5 + 0.5 * spread);
|
||||
let f = pv(parameters.frequency, i);
|
||||
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
||||
const normalizer = 0.3 / voices;
|
||||
for (let n = 0; n < voices; n++) {
|
||||
const isOdd = (n & 1) == 1;
|
||||
let gainL = gain1;
|
||||
@@ -1274,19 +1275,19 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
|
||||
const dPhase = fVoice / sampleRate;
|
||||
const level = this._chooseMip(dPhase);
|
||||
const bank = this.tables[level];
|
||||
const table = this.tables[level];
|
||||
|
||||
// warp phase then sample
|
||||
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
||||
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||
const s0 = this._sampleFrame(bank[fIdx], ph);
|
||||
const s1 = this._sampleFrame(bank[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
||||
const s0 = this._sampleFrame(table[fIdx], ph);
|
||||
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
||||
let s = s0 + (s1 - s0) * frac;
|
||||
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||
s = -s;
|
||||
}
|
||||
outL[i] += ((s * gainL) / Math.sqrt(voices)) * gainAdjustment;
|
||||
outR[i] += ((s * gainR) / Math.sqrt(voices)) * gainAdjustment;
|
||||
outL[i] += s * gainL * normalizer;
|
||||
outR[i] += s * gainR * normalizer;
|
||||
this.phase[n] = wrapPhase(this.phase[n] + dPhase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11993,6 +11993,234 @@ exports[`runs examples > example "withValue" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "wtPhaseRand" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 1/16 → 1/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 1/8 → 3/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 3/16 → 1/4 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 1/4 → 5/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 5/16 → 3/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 3/8 → 7/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 7/16 → 1/2 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 1/2 → 9/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 9/16 → 5/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 5/8 → 11/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 11/16 → 3/4 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 3/4 → 13/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 13/16 → 7/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 7/8 → 15/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 15/16 → 1/1 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 1/1 → 17/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 17/16 → 9/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 9/8 → 19/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 19/16 → 5/4 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 5/4 → 21/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 21/16 → 11/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 11/8 → 23/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 23/16 → 3/2 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 3/2 → 25/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 25/16 → 13/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 13/8 → 27/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 27/16 → 7/4 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 7/4 → 29/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 29/16 → 15/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 15/8 → 31/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 31/16 → 2/1 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 2/1 → 33/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 33/16 → 17/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 17/8 → 35/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 35/16 → 9/4 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 9/4 → 37/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 37/16 → 19/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 19/8 → 39/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 39/16 → 5/2 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 5/2 → 41/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 41/16 → 21/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 21/8 → 43/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 43/16 → 11/4 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 11/4 → 45/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 45/16 → 23/8 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 23/8 → 47/16 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 47/16 → 3/1 | s:basique bank:wt_digital wtPhaseRand:0 ]",
|
||||
"[ 3/1 → 49/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 49/16 → 25/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 25/8 → 51/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 51/16 → 13/4 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 13/4 → 53/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 53/16 → 27/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 27/8 → 55/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 55/16 → 7/2 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 7/2 → 57/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 57/16 → 29/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 29/8 → 59/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 59/16 → 15/4 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 15/4 → 61/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 61/16 → 31/8 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 31/8 → 63/16 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
"[ 63/16 → 4/1 | s:basique bank:wt_digital wtPhaseRand:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "wtPos" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ (1/8 → 1/5) ⇝ 1/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ 1/8 ⇜ (1/5 → 1/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 1/4 → 3/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ (3/8 → 2/5) ⇝ 1/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 3/8 ⇜ (2/5 → 1/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ (1/2 → 3/5) ⇝ 5/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ 1/2 ⇜ (3/5 → 5/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 5/8 → 3/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ (3/4 → 4/5) ⇝ 7/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 3/4 ⇜ (4/5 → 7/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
"[ 7/8 → 1/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
"[ 1/1 → 9/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ (9/8 → 6/5) ⇝ 5/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ 9/8 ⇜ (6/5 → 5/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 5/4 → 11/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ (11/8 → 7/5) ⇝ 3/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 11/8 ⇜ (7/5 → 3/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ (3/2 → 8/5) ⇝ 13/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ 3/2 ⇜ (8/5 → 13/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 13/8 → 7/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ (7/4 → 9/5) ⇝ 15/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 7/4 ⇜ (9/5 → 15/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
"[ 15/8 → 2/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
"[ 2/1 → 17/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ (17/8 → 11/5) ⇝ 9/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ 17/8 ⇜ (11/5 → 9/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 9/4 → 19/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ (19/8 → 12/5) ⇝ 5/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 19/8 ⇜ (12/5 → 5/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ (5/2 → 13/5) ⇝ 21/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ 5/2 ⇜ (13/5 → 21/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 21/8 → 11/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ (11/4 → 14/5) ⇝ 23/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 11/4 ⇜ (14/5 → 23/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
"[ 23/8 → 3/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
"[ 3/1 → 25/8 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ (25/8 → 16/5) ⇝ 13/4 | s:squelch bank:wt_digital note:F1 wtPos:0 ]",
|
||||
"[ 25/8 ⇜ (16/5 → 13/4) | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 13/4 → 27/8 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ (27/8 → 17/5) ⇝ 7/2 | s:squelch bank:wt_digital note:F1 wtPos:0.25 ]",
|
||||
"[ 27/8 ⇜ (17/5 → 7/2) | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ (7/2 → 18/5) ⇝ 29/8 | s:squelch bank:wt_digital note:F1 wtPos:0.5 ]",
|
||||
"[ 7/2 ⇜ (18/5 → 29/8) | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 29/8 → 15/4 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ (15/4 → 19/5) ⇝ 31/8 | s:squelch bank:wt_digital note:F1 wtPos:0.75 ]",
|
||||
"[ 15/4 ⇜ (19/5 → 31/8) | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
"[ 31/8 → 4/1 | s:squelch bank:wt_digital note:F1 wtPos:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "wtWarp" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ (1/8 → 1/5) ⇝ 1/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ 1/8 ⇜ (1/5 → 1/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 1/4 → 3/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ (3/8 → 2/5) ⇝ 1/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 3/8 ⇜ (2/5 → 1/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ (1/2 → 3/5) ⇝ 5/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ 1/2 ⇜ (3/5 → 5/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 5/8 → 3/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ (3/4 → 4/5) ⇝ 7/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 3/4 ⇜ (4/5 → 7/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
"[ 7/8 → 1/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
"[ 1/1 → 9/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ (9/8 → 6/5) ⇝ 5/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ 9/8 ⇜ (6/5 → 5/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 5/4 → 11/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ (11/8 → 7/5) ⇝ 3/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 11/8 ⇜ (7/5 → 3/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ (3/2 → 8/5) ⇝ 13/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ 3/2 ⇜ (8/5 → 13/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 13/8 → 7/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ (7/4 → 9/5) ⇝ 15/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 7/4 ⇜ (9/5 → 15/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
"[ 15/8 → 2/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
"[ 2/1 → 17/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ (17/8 → 11/5) ⇝ 9/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ 17/8 ⇜ (11/5 → 9/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 9/4 → 19/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ (19/8 → 12/5) ⇝ 5/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 19/8 ⇜ (12/5 → 5/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ (5/2 → 13/5) ⇝ 21/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ 5/2 ⇜ (13/5 → 21/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 21/8 → 11/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ (11/4 → 14/5) ⇝ 23/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 11/4 ⇜ (14/5 → 23/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
"[ 23/8 → 3/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
"[ 3/1 → 25/8 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ (25/8 → 16/5) ⇝ 13/4 | s:basique bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ 25/8 ⇜ (16/5 → 13/4) | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 13/4 → 27/8 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ (27/8 → 17/5) ⇝ 7/2 | s:basique bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 27/8 ⇜ (17/5 → 7/2) | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ (7/2 → 18/5) ⇝ 29/8 | s:basique bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ 7/2 ⇜ (18/5 → 29/8) | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 29/8 → 15/4 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ (15/4 → 19/5) ⇝ 31/8 | s:basique bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:spin ]",
|
||||
"[ 15/4 ⇜ (19/5 → 31/8) | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
"[ 31/8 → 4/1 | s:basique bank:wt_digital note:F1 wtWarp:1 wtWarpMode:spin ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "wtWarpMode" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:asym ]",
|
||||
"[ (1/8 → 1/5) ⇝ 1/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:asym ]",
|
||||
"[ 1/8 ⇜ (1/5 → 1/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:asym ]",
|
||||
"[ 1/4 → 3/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:asym ]",
|
||||
"[ (3/8 → 2/5) ⇝ 1/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:asym ]",
|
||||
"[ 3/8 ⇜ (2/5 → 1/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:asym ]",
|
||||
"[ (1/2 → 3/5) ⇝ 5/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:bendp ]",
|
||||
"[ 1/2 ⇜ (3/5 → 5/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:bendp ]",
|
||||
"[ 5/8 → 3/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:bendp ]",
|
||||
"[ (3/4 → 4/5) ⇝ 7/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:bendp ]",
|
||||
"[ 3/4 ⇜ (4/5 → 7/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:bendp ]",
|
||||
"[ 7/8 → 1/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:bendp ]",
|
||||
"[ 1/1 → 9/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ (9/8 → 6/5) ⇝ 5/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:spin ]",
|
||||
"[ 9/8 ⇜ (6/5 → 5/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 5/4 → 11/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ (11/8 → 7/5) ⇝ 3/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:spin ]",
|
||||
"[ 11/8 ⇜ (7/5 → 3/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:spin ]",
|
||||
"[ (3/2 → 8/5) ⇝ 13/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:logistic ]",
|
||||
"[ 3/2 ⇜ (8/5 → 13/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:logistic ]",
|
||||
"[ 13/8 → 7/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:logistic ]",
|
||||
"[ (7/4 → 9/5) ⇝ 15/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:logistic ]",
|
||||
"[ 7/4 ⇜ (9/5 → 15/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:logistic ]",
|
||||
"[ 15/8 → 2/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:logistic ]",
|
||||
"[ 2/1 → 17/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:sync ]",
|
||||
"[ (17/8 → 11/5) ⇝ 9/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:sync ]",
|
||||
"[ 17/8 ⇜ (11/5 → 9/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:sync ]",
|
||||
"[ 9/4 → 19/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:sync ]",
|
||||
"[ (19/8 → 12/5) ⇝ 5/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:sync ]",
|
||||
"[ 19/8 ⇜ (12/5 → 5/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:sync ]",
|
||||
"[ (5/2 → 13/5) ⇝ 21/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:wormhole ]",
|
||||
"[ 5/2 ⇜ (13/5 → 21/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:wormhole ]",
|
||||
"[ 21/8 → 11/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:wormhole ]",
|
||||
"[ (11/4 → 14/5) ⇝ 23/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:wormhole ]",
|
||||
"[ 11/4 ⇜ (14/5 → 23/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:wormhole ]",
|
||||
"[ 23/8 → 3/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:wormhole ]",
|
||||
"[ 3/1 → 25/8 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:brownian ]",
|
||||
"[ (25/8 → 16/5) ⇝ 13/4 | s:morgana bank:wt_digital note:F1 wtWarp:0 wtWarpMode:brownian ]",
|
||||
"[ 25/8 ⇜ (16/5 → 13/4) | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:brownian ]",
|
||||
"[ 13/4 → 27/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:brownian ]",
|
||||
"[ (27/8 → 17/5) ⇝ 7/2 | s:morgana bank:wt_digital note:F1 wtWarp:0.25 wtWarpMode:brownian ]",
|
||||
"[ 27/8 ⇜ (17/5 → 7/2) | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:brownian ]",
|
||||
"[ (7/2 → 18/5) ⇝ 29/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.5 wtWarpMode:asym ]",
|
||||
"[ 7/2 ⇜ (18/5 → 29/8) | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:asym ]",
|
||||
"[ 29/8 → 15/4 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:asym ]",
|
||||
"[ (15/4 → 19/5) ⇝ 31/8 | s:morgana bank:wt_digital note:F1 wtWarp:0.75 wtWarpMode:asym ]",
|
||||
"[ 15/4 ⇜ (19/5 → 31/8) | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:asym ]",
|
||||
"[ 31/8 → 4/1 | s:morgana bank:wt_digital note:F1 wtWarp:1 wtWarpMode:asym ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "xfade" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh gain:0 ]",
|
||||
|
||||
@@ -18,5 +18,22 @@
|
||||
"wt_digital/wt_squelch.wav",
|
||||
"wt_digital/wt_summer.wav",
|
||||
"wt_digital/wt_wasp.wav"
|
||||
]
|
||||
],
|
||||
"wt_digital_bad_day": ["wt_digital/wt_bad_day.wav"],
|
||||
"wt_digital_basique": ["wt_digital/wt_basique.wav"],
|
||||
"wt_digital_crickets": ["wt_digital/wt_crickets.wav"],
|
||||
"wt_digital_curses": ["wt_digital/wt_curses.wav"],
|
||||
"wt_digital_earl_grey": ["wt_digital/wt_earl_grey.wav"],
|
||||
"wt_digital_echoes": ["wt_digital/wt_echoes.wav"],
|
||||
"wt_digital_glimmer": ["wt_digital/wt_glimmer.wav"],
|
||||
"wt_digital_majick": ["wt_digital/wt_majick.wav"],
|
||||
"wt_digital_meditation": ["wt_digital/wt_meditation.wav"],
|
||||
"wt_digital_morgana": ["wt_digital/wt_morgana.wav"],
|
||||
"wt_digital_red_alert": ["wt_digital/wt_red_alert.wav"],
|
||||
"wt_digital_sad_piano": ["wt_digital/wt_sad_piano.wav"],
|
||||
"wt_digital_shook": ["wt_digital/wt_shook.wav"],
|
||||
"wt_digital_sludge": ["wt_digital/wt_sludge.wav"],
|
||||
"wt_digital_squelch": ["wt_digital/wt_squelch.wav"],
|
||||
"wt_digital_summer": ["wt_digital/wt_summer.wav"],
|
||||
"wt_digital_wasp": ["wt_digital/wt_wasp.wav"]
|
||||
}
|
||||
@@ -44,6 +44,9 @@ export function SoundsTab() {
|
||||
if (soundsFilter === soundFilterType.SYNTHS) {
|
||||
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
|
||||
if (soundsFilter === 'importSounds') {
|
||||
return [];
|
||||
@@ -54,6 +57,9 @@ export function SoundsTab() {
|
||||
// holds mutable ref to current triggered sound
|
||||
const trigRef = useRef();
|
||||
|
||||
// Used to cycle through sound previews on banks with multiple sounds
|
||||
let soundPreviewIdx = 0;
|
||||
|
||||
// stop current sound on mouseup
|
||||
useEvent('mouseup', () => {
|
||||
const t = trigRef.current;
|
||||
@@ -74,6 +80,7 @@ export function SoundsTab() {
|
||||
samples: 'samples',
|
||||
drums: 'drum-machines',
|
||||
synths: 'Synths',
|
||||
wavetables: 'Wavetables',
|
||||
user: 'User',
|
||||
importSounds: 'import-sounds',
|
||||
}}
|
||||
@@ -110,11 +117,13 @@ export function SoundsTab() {
|
||||
const params = {
|
||||
note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined,
|
||||
s: name,
|
||||
n: soundPreviewIdx,
|
||||
clip: 1,
|
||||
release: 0.5,
|
||||
sustain: 1,
|
||||
duration: 0.5,
|
||||
};
|
||||
soundPreviewIdx++;
|
||||
const time = ctx.currentTime + 0.05;
|
||||
const onended = () => trigRef.current?.node?.disconnect();
|
||||
trigRef.current = Promise.resolve(onTrigger(time, params, onended));
|
||||
@@ -126,6 +135,7 @@ export function SoundsTab() {
|
||||
{' '}
|
||||
{name}
|
||||
{data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''}
|
||||
{data?.type === 'wavetable' ? `(${getSamples(data.tables)})` : ''}
|
||||
{data?.type === 'soundfont' ? `(${data.fonts.length})` : ''}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ export const soundFilterType = {
|
||||
DRUMS: 'drums',
|
||||
SAMPLES: 'samples',
|
||||
SYNTHS: 'synths',
|
||||
WAVETABLES: 'wavetables',
|
||||
ALL: 'all',
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user