Merge remote-tracking branch 'origin/main' into glossing/sounds-tab-preview
This commit is contained in:
parent
74488f8480
commit
b824c45ba4
26 changed files with 777 additions and 133 deletions
|
|
@ -1294,6 +1294,8 @@ export const { fanchor } = registerControl('fanchor');
|
|||
*
|
||||
* @name lprate
|
||||
* @param {number | Pattern} rate rate in hertz
|
||||
* @example
|
||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lprate("<4 8 2 1>")
|
||||
*/
|
||||
export const { lprate } = registerControl('lprate');
|
||||
|
||||
|
|
@ -1302,6 +1304,8 @@ export const { lprate } = registerControl('lprate');
|
|||
*
|
||||
* @name lpsync
|
||||
* @param {number | Pattern} rate rate in cycles
|
||||
* @example
|
||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpsync("<4 8 2 1>")
|
||||
*/
|
||||
export const { lpsync } = registerControl('lpsync');
|
||||
|
||||
|
|
@ -1310,8 +1314,23 @@ export const { lpsync } = registerControl('lpsync');
|
|||
*
|
||||
* @name lpdepth
|
||||
* @param {number | Pattern} depth depth of modulation
|
||||
* @example
|
||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepth("<1 .5 1.8 0>")
|
||||
*/
|
||||
|
||||
export const { lpdepth } = registerControl('lpdepth');
|
||||
/**
|
||||
* Depth of the LFO for the lowpass filter, in HZ
|
||||
*
|
||||
* @name lpdepthfrequency
|
||||
* @synonyms
|
||||
* lpdethfreq
|
||||
* @param {number | Pattern} depth depth of modulation
|
||||
* @example
|
||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
|
||||
*/
|
||||
|
||||
export const { lpdepthfrequency } = registerControl('lpdepthfrequency', 'lpdepthfreq');
|
||||
|
||||
/**
|
||||
* Shape of the LFO for the lowpass filter
|
||||
|
|
@ -1361,6 +1380,19 @@ export const { bpsync } = registerControl('bpsync');
|
|||
*/
|
||||
export const { bpdepth } = registerControl('bpdepth');
|
||||
|
||||
/**
|
||||
* Depth of the LFO for the bandpass filter, in HZ
|
||||
*
|
||||
* @name bpdepthfrequency
|
||||
* @synonyms
|
||||
* bpdethfreq
|
||||
* @param {number | Pattern} depth depth of modulation
|
||||
* @example
|
||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
|
||||
*/
|
||||
|
||||
export const { bpdepthfrequency } = registerControl('bpdepthfrequency', 'bpdepthfreq');
|
||||
|
||||
/**
|
||||
* Shape of the LFO for the bandpass filter
|
||||
*
|
||||
|
|
@ -1407,7 +1439,20 @@ export const { hpsync } = registerControl('hpsync');
|
|||
* @name hpdepth
|
||||
* @param {number | Pattern} depth depth of modulation
|
||||
*/
|
||||
export const { hpdepth } = registerControl('hpdepth');
|
||||
export const { hpdepth, hpdepthfreq } = registerControl('hpdepth');
|
||||
|
||||
/**
|
||||
* Depth of the LFO for the hipass filter, in hz
|
||||
*
|
||||
* @name hpdepthfrequency
|
||||
* @synonyms
|
||||
* hpdethfreq
|
||||
* @param {number | Pattern} depth depth of modulation
|
||||
* @example
|
||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
|
||||
*/
|
||||
|
||||
export const { hpdepthfrequency } = registerControl('hpdepthfrequency', 'hpdepthfreq');
|
||||
|
||||
/**
|
||||
* Shape of the LFO for the highpass filter
|
||||
|
|
@ -1820,12 +1865,12 @@ export const { nudge } = registerControl('nudge');
|
|||
* Sets the default octave of a synth.
|
||||
*
|
||||
* @name octave
|
||||
* @synonyms oct
|
||||
* @param {number | Pattern} octave octave number
|
||||
* @example
|
||||
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
|
||||
* @superDirtOnly
|
||||
* n("0,4,7").scale("F:minor").s('supersaw').octave("<0 1 2 3>")
|
||||
*/
|
||||
export const { octave } = registerControl('octave');
|
||||
export const { octave, oct } = registerControl('octave', 'oct');
|
||||
|
||||
// ['ophatdecay'],
|
||||
// TODO: example
|
||||
|
|
@ -1833,6 +1878,7 @@ export const { octave } = registerControl('octave');
|
|||
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
||||
*
|
||||
* @name orbit
|
||||
* @synonyms o
|
||||
* @param {number | Pattern} number
|
||||
* @example
|
||||
* stack(
|
||||
|
|
@ -1840,7 +1886,7 @@ export const { octave } = registerControl('octave');
|
|||
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||
* )
|
||||
*/
|
||||
export const { orbit } = registerControl('orbit');
|
||||
export const { orbit } = registerControl('orbit', 'o');
|
||||
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
|
||||
export const { overgain } = registerControl('overgain');
|
||||
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
stringifyValues,
|
||||
} from './util.mjs';
|
||||
import drawLine from './drawLine.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { errorLogger, logger } from './logger.mjs';
|
||||
|
||||
let stringParser;
|
||||
|
||||
|
|
@ -414,7 +414,7 @@ export class Pattern {
|
|||
try {
|
||||
return this.query(new State(new TimeSpan(begin, end), controls));
|
||||
} catch (err) {
|
||||
logger(`[query]: ${err.message}`, 'error');
|
||||
errorLogger(err, 'query');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,9 +152,9 @@ export function repl({
|
|||
// allows muting a pattern x with x_ or _x
|
||||
return silence;
|
||||
}
|
||||
if (id === '$') {
|
||||
if (id.includes('$')) {
|
||||
// allows adding anonymous patterns with $:
|
||||
id = `$${anonymousIndex}`;
|
||||
id = `${id}${anonymousIndex}`;
|
||||
anonymousIndex++;
|
||||
}
|
||||
pPatterns[id] = this;
|
||||
|
|
@ -215,8 +215,19 @@ export function repl({
|
|||
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
||||
if (Object.keys(pPatterns).length) {
|
||||
let patterns = [];
|
||||
let soloActive = false;
|
||||
for (const [key, value] of Object.entries(pPatterns)) {
|
||||
patterns.push(value.withState((state) => state.setControls({ id: key })));
|
||||
// handle soloed patterns ex: S$: s("bd!4")
|
||||
const isSolod = key.length > 1 && key.startsWith('S');
|
||||
if (isSolod && soloActive === false) {
|
||||
// first time we see a soloed pattern, clear existing patterns
|
||||
patterns = [];
|
||||
soloActive = true;
|
||||
}
|
||||
if (!soloActive || (soloActive && isSolod)) {
|
||||
const valWithState = value.withState((state) => state.setControls({ id: key }));
|
||||
patterns.push(valWithState);
|
||||
}
|
||||
}
|
||||
if (eachTransform) {
|
||||
// Explicit lambda so only element (not index and array) are passed
|
||||
|
|
@ -227,8 +238,8 @@ export function repl({
|
|||
pattern = eachTransform(pattern);
|
||||
}
|
||||
if (allTransforms.length) {
|
||||
for (let i in allTransforms) {
|
||||
pattern = allTransforms[i](pattern);
|
||||
for (const transform of allTransforms) {
|
||||
pattern = transform(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
|
|||
* @example
|
||||
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
|
||||
* @example
|
||||
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
|
||||
* wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note()
|
||||
* @example
|
||||
* // The probability can itself be a pattern
|
||||
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export async function initHydra(options = {}) {
|
|||
hydra.synth.s0.init({ src: canvas });
|
||||
}
|
||||
}
|
||||
return hydra;
|
||||
}
|
||||
|
||||
export function clearHydra() {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import readline from 'readline';
|
|||
import os from 'os';
|
||||
|
||||
const LOG = !!process.env.LOG || false;
|
||||
const PORT = process.env.PORT || 5432;
|
||||
const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg'];
|
||||
|
||||
const isAudioFile = (f) => {
|
||||
|
|
@ -54,7 +55,6 @@ async function getBanks(directory, flat = false) {
|
|||
banks[bank].push(subDir);
|
||||
return subDir;
|
||||
});
|
||||
banks._base = `http://localhost:5432`;
|
||||
return { banks, files };
|
||||
}
|
||||
|
||||
|
|
@ -134,8 +134,6 @@ const server = http.createServer(async (req, res) => {
|
|||
readStream.pipe(res);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line
|
||||
const PORT = process.env.PORT || 5432;
|
||||
const IP_ADDRESS = '0.0.0.0';
|
||||
let IP;
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
|
|
|
|||
|
|
@ -191,8 +191,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
|
|||
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
|
||||
return { lfo, disconnect: () => lfo?.disconnect() };
|
||||
}
|
||||
|
||||
export function createFilter(context, start, end, params, cps) {
|
||||
export function createFilter(context, start, end, params, cps, cycle) {
|
||||
let {
|
||||
frequency,
|
||||
anchor,
|
||||
|
|
@ -202,12 +201,14 @@ export function createFilter(context, start, end, params, cps) {
|
|||
q = 1,
|
||||
drive = 0.69,
|
||||
depth,
|
||||
depthfrequency,
|
||||
dcoffset = -0.5,
|
||||
skew,
|
||||
shape,
|
||||
rate,
|
||||
sync,
|
||||
} = params;
|
||||
|
||||
let frequencyParam, filter;
|
||||
if (model === 'ladder') {
|
||||
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
|
||||
|
|
@ -238,8 +239,25 @@ export function createFilter(context, start, end, params, cps) {
|
|||
if (sync != null) {
|
||||
rate = cps * sync;
|
||||
}
|
||||
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate };
|
||||
getParamLfo(context, frequencyParam, start, end, lfoValues);
|
||||
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
|
||||
if (hasLFO) {
|
||||
depth = depth ?? 1;
|
||||
const time = cycle / cps;
|
||||
const modDepth = depthfrequency ?? (depth ?? 1) * frequency;
|
||||
const lfoValues = {
|
||||
depth: modDepth,
|
||||
dcoffset,
|
||||
skew,
|
||||
shape,
|
||||
frequency: rate ?? cps,
|
||||
min: -frequency + 30,
|
||||
max: 20000 - frequency,
|
||||
time,
|
||||
curve: 1,
|
||||
};
|
||||
getParamLfo(context, frequencyParam, start, end, lfoValues);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +280,15 @@ export function drywet(dry, wet, wetAmount = 0) {
|
|||
let mix = ac.createGain();
|
||||
dry_gain.connect(mix);
|
||||
wet_gain.connect(mix);
|
||||
return mix;
|
||||
return {
|
||||
node: mix,
|
||||
onended: () => {
|
||||
dry_gain.disconnect(mix);
|
||||
wet_gain.disconnect(mix);
|
||||
dry.disconnect(dry_gain);
|
||||
wet.disconnect(wet_gain);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let curves = ['linear', 'exponential'];
|
||||
|
|
@ -297,6 +323,10 @@ export function getVibratoOscillator(param, value, t) {
|
|||
gain.gain.value = vibmod * 100;
|
||||
vibratoOscillator.connect(gain);
|
||||
gain.connect(param);
|
||||
vibratoOscillator.onended = () => {
|
||||
gain.disconnect(param);
|
||||
vibratoOscillator.disconnect(gain);
|
||||
};
|
||||
vibratoOscillator.start(t);
|
||||
return vibratoOscillator;
|
||||
}
|
||||
|
|
@ -350,9 +380,9 @@ const mod = (freq, range = 1, type = 'sine') => {
|
|||
}
|
||||
|
||||
osc.start();
|
||||
const g = new GainNode(ctx, { gain: range });
|
||||
const g = gainNode(range);
|
||||
osc.connect(g); // -range, range
|
||||
return { node: g, stop: (t) => osc.stop(t) };
|
||||
return { node: g, stop: (t) => osc.stop(t), osc: osc };
|
||||
};
|
||||
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
||||
const carrfreq = frequencyparam.value;
|
||||
|
|
@ -404,6 +434,11 @@ export function applyFM(param, value, begin) {
|
|||
modulator.connect(envGain);
|
||||
envGain.connect(param);
|
||||
}
|
||||
fmmod.osc.onended = () => {
|
||||
envGain.disconnect();
|
||||
modulator.disconnect();
|
||||
fmmod.osc.disconnect();
|
||||
};
|
||||
}
|
||||
return { stop };
|
||||
}
|
||||
|
|
@ -505,7 +540,7 @@ export const getDistortion = (distort, postgain, algorithm) => {
|
|||
};
|
||||
|
||||
export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||
let { note, freq } = value;
|
||||
let { note, freq, octave = 0 } = value;
|
||||
note = note || defaultNote;
|
||||
if (typeof note === 'string') {
|
||||
note = noteToMidi(note); // e.g. c3 => 48
|
||||
|
|
@ -514,7 +549,7 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
|||
if (!freq && typeof note === 'number') {
|
||||
freq = midiToFreq(note); // + 48);
|
||||
}
|
||||
|
||||
freq *= Math.pow(2, octave);
|
||||
return Number(freq);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) {
|
|||
export function getNoiseMix(inputNode, wet, t) {
|
||||
const noiseOscillator = getNoiseOscillator('pink', t);
|
||||
const noiseMix = drywet(inputNode, noiseOscillator.node, wet);
|
||||
noiseOscillator.node.onended = () => noiseMix.onended();
|
||||
return {
|
||||
node: noiseMix,
|
||||
node: noiseMix.node,
|
||||
stop: (time) => noiseOscillator?.stop(time),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getCommonSampleInfo } from './util.mjs';
|
||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||
import { registerSound, registerWaveTable } from './index.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
||||
|
|
@ -211,7 +211,7 @@ export async function fetchSampleMap(url) {
|
|||
// not a browser
|
||||
return;
|
||||
}
|
||||
const base = url.split('/').slice(0, -1).join('/');
|
||||
const base = getBaseURL(url);
|
||||
if (typeof fetch === 'undefined') {
|
||||
// skip fetch when in node / testing
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -553,13 +553,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||
rate: 'lprate',
|
||||
sync: 'lpsync',
|
||||
depth: 'lpdepth',
|
||||
depthfrequency: 'lpdepthfrequency',
|
||||
shape: 'lpshape',
|
||||
dcoffset: 'lpdc',
|
||||
skew: 'lpskew',
|
||||
};
|
||||
const lpParams = pickAndRename(value, lpMap);
|
||||
lpParams.type = 'lowpass';
|
||||
let lp = () => createFilter(ac, t, end, lpParams, cps);
|
||||
let lp = () => createFilter(ac, t, end, lpParams, cps, cycle);
|
||||
chain.push(lp());
|
||||
if (ftype === '24db') {
|
||||
chain.push(lp());
|
||||
|
|
@ -581,13 +582,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||
rate: 'hprate',
|
||||
sync: 'hpsync',
|
||||
depth: 'hpdepth',
|
||||
depthfrequency: 'hpdepthfrequency',
|
||||
shape: 'hpshape',
|
||||
dcoffset: 'hpdc',
|
||||
skew: 'hpskew',
|
||||
};
|
||||
const hpParams = pickAndRename(value, hpMap);
|
||||
hpParams.type = 'highpass';
|
||||
let hp = () => createFilter(ac, t, end, hpParams, cps);
|
||||
let hp = () => createFilter(ac, t, end, hpParams, cps, cycle);
|
||||
chain.push(hp());
|
||||
if (ftype === '24db') {
|
||||
chain.push(hp());
|
||||
|
|
@ -609,13 +611,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||
rate: 'bprate',
|
||||
sync: 'bpsync',
|
||||
depth: 'bpdepth',
|
||||
depthfrequency: 'bpdepthfrequency',
|
||||
shape: 'bpshape',
|
||||
dcoffset: 'bpdc',
|
||||
skew: 'bpskew',
|
||||
};
|
||||
const bpParams = pickAndRename(value, bpMap);
|
||||
bpParams.type = 'bandpass';
|
||||
let bp = () => createFilter(ac, t, end, bpParams, cps);
|
||||
let bp = () => createFilter(ac, t, end, bpParams, cps, cycle);
|
||||
chain.push(bp());
|
||||
if (ftype === '24db') {
|
||||
chain.push(bp());
|
||||
|
|
@ -665,6 +668,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||
curve: 1.5,
|
||||
});
|
||||
lfo.connect(amGain.gain);
|
||||
audioNodes.push(lfo);
|
||||
chain.push(amGain);
|
||||
}
|
||||
|
||||
|
|
@ -692,7 +696,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||
// delay
|
||||
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||
orbitBus.getDelay(delaytime, delayfeedback, t);
|
||||
orbitBus.sendDelay(post, delay);
|
||||
const send = orbitBus.sendDelay(post, delay);
|
||||
audioNodes.push(send);
|
||||
}
|
||||
// reverb
|
||||
if (room > 0) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export class Orbit {
|
|||
}
|
||||
|
||||
sendDelay(node, amount) {
|
||||
effectSend(node, this.delayNode, amount);
|
||||
return effectSend(node, this.delayNode, amount);
|
||||
}
|
||||
|
||||
duck(t, onsettime = 0, attacktime = 0.1, depth = 1) {
|
||||
|
|
|
|||
|
|
@ -48,19 +48,17 @@ export function registerSynthSounds() {
|
|||
[0.001, 0.05, 0.6, 0.01],
|
||||
);
|
||||
|
||||
let sound = getOscillator(s, t, value);
|
||||
let { node: o, stop, triggerRelease } = sound;
|
||||
|
||||
// turn down
|
||||
const g = gainNode(0.3);
|
||||
|
||||
const { duration } = value;
|
||||
|
||||
o.onended = () => {
|
||||
o.disconnect();
|
||||
let sound = getOscillator(s, t, value, () => {
|
||||
g.disconnect();
|
||||
onended();
|
||||
};
|
||||
});
|
||||
|
||||
let { node: o, stop, triggerRelease } = sound;
|
||||
|
||||
const { duration } = value;
|
||||
|
||||
const envGain = gainNode(1);
|
||||
let node = o.connect(g).connect(envGain);
|
||||
|
|
@ -460,7 +458,7 @@ export function waveformN(partials, phases, type) {
|
|||
}
|
||||
|
||||
// expects one of waveforms as s
|
||||
export function getOscillator(s, t, value) {
|
||||
export function getOscillator(s, t, value, onended) {
|
||||
const { duration, noise = 0 } = value;
|
||||
const partials = value.partials ?? value.n;
|
||||
let o;
|
||||
|
|
@ -482,7 +480,6 @@ export function getOscillator(s, t, value) {
|
|||
}
|
||||
// set frequency
|
||||
o.frequency.value = getFrequencyFromValue(value);
|
||||
o.start(t);
|
||||
|
||||
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
|
||||
|
||||
|
|
@ -495,6 +492,13 @@ export function getOscillator(s, t, value) {
|
|||
noiseMix = getNoiseMix(o, noise, t);
|
||||
}
|
||||
|
||||
o.onended = () => {
|
||||
o.disconnect();
|
||||
noiseMix?.node.disconnect();
|
||||
onended();
|
||||
};
|
||||
o.start(t);
|
||||
|
||||
return {
|
||||
node: noiseMix?.node || o,
|
||||
stop: (time) => {
|
||||
|
|
|
|||
|
|
@ -114,3 +114,13 @@ export function getCommonSampleInfo(hapValue, bank) {
|
|||
export const pickAndRename = (source, map) => {
|
||||
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
|
||||
};
|
||||
|
||||
export const getBaseURL = (url) => {
|
||||
try {
|
||||
// For real URLs
|
||||
return new URL('.', new URL(url)).href.replace(/\/$/, ''); // removes trailing slash
|
||||
} catch {
|
||||
// For pseudo URLS
|
||||
return url.split('/').slice(0, -1).join('/');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ if (typeof GainNode !== 'undefined') {
|
|||
throw new Error('vowel: unknown vowel ' + letter);
|
||||
}
|
||||
const { gains, qs, freqs } = vowelFormant[letter];
|
||||
const makeupGain = ac.createGain();
|
||||
this.makeupGain = ac.createGain();
|
||||
this.audioNodes = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const gain = ac.createGain();
|
||||
gain.gain.value = gains[i];
|
||||
|
|
@ -53,14 +54,25 @@ if (typeof GainNode !== 'undefined') {
|
|||
filter.type = 'bandpass';
|
||||
filter.Q.value = qs[i];
|
||||
filter.frequency.value = freqs[i];
|
||||
this.connect(filter);
|
||||
super.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(makeupGain);
|
||||
this.audioNodes.push(filter);
|
||||
gain.connect(this.makeupGain);
|
||||
this.audioNodes.push(gain);
|
||||
}
|
||||
makeupGain.gain.value = 8; // how much makeup gain to add?
|
||||
this.connect = (target) => makeupGain.connect(target);
|
||||
this.makeupGain.gain.value = 8; // how much makeup gain to add?
|
||||
return this;
|
||||
}
|
||||
connect(target) {
|
||||
this.makeupGain.connect(target);
|
||||
}
|
||||
disconnect() {
|
||||
this.makeupGain.disconnect();
|
||||
this.audioNodes.forEach((n) => n.disconnect());
|
||||
super.disconnect();
|
||||
this.makeupGain = null;
|
||||
this.audioNodes = null;
|
||||
}
|
||||
}
|
||||
|
||||
AudioContext.prototype.createVowelFilter = function (letter) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getAudioContext, registerSound } from './index.mjs';
|
||||
import { getCommonSampleInfo } from './util.mjs';
|
||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||
import {
|
||||
applyFM,
|
||||
applyParameterModulators,
|
||||
|
|
@ -190,6 +190,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
|
|||
if (url.startsWith('local:')) {
|
||||
url = `http://localhost:5432`;
|
||||
}
|
||||
const base = getBaseURL(url);
|
||||
if (typeof fetch !== 'function') {
|
||||
// not a browser
|
||||
return;
|
||||
|
|
@ -200,7 +201,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
|
|||
}
|
||||
return fetch(url)
|
||||
.then((res) => res.json())
|
||||
.then((json) => _processTables(json, url, frameLen, options))
|
||||
.then((json) => _processTables(json, base, frameLen, options))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
throw new Error(`error loading "${url}"`);
|
||||
|
|
|
|||
|
|
@ -532,7 +532,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
|
||||
// We must wrap this here because it is passed into sawblep below which
|
||||
// has domain [0, 1]
|
||||
const dt = ffrac(freqVoice * INVSR);
|
||||
const dt = frac(freqVoice * INVSR);
|
||||
this.phase[n] = this.phase[n] ?? Math.random();
|
||||
const v = waveshapes.sawblep(this.phase[n], dt);
|
||||
|
||||
|
|
@ -543,8 +543,9 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||
if (pn >= 1.0) pn -= 1.0;
|
||||
this.phase[n] = pn;
|
||||
// invert right and left gain
|
||||
const tmp = gainL;
|
||||
gainL = gainR;
|
||||
gainR = gainL;
|
||||
gainR = tmp;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
@ -1338,7 +1339,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||
const tablePos = clamp(pv(parameters.position, i), 0, 1);
|
||||
const idx = tablePos * (this.numFrames - 1);
|
||||
const fIdx = idx | 0;
|
||||
const frac = idx - fIdx;
|
||||
const interpT = idx - fIdx;
|
||||
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
|
||||
const warpMode = pv(parameters.warpMode, i);
|
||||
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
|
||||
|
|
@ -1368,13 +1369,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||
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;
|
||||
let s = lerp(s0, s1, interpT);
|
||||
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||
s = -s;
|
||||
}
|
||||
outL[i] += s * gainL * normalizer;
|
||||
outR[i] += s * gainR * normalizer;
|
||||
this.phase[n] = ffrac(this.phase[n] + dPhase);
|
||||
this.phase[n] = frac(this.phase[n] + dPhase);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,16 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||
*/
|
||||
|
||||
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
||||
import { register, _mod, logger, isNote, noteToMidi, removeUndefineds, getAccidentalsOffset } from '@strudel/core';
|
||||
import {
|
||||
_mod,
|
||||
errorLogger,
|
||||
getAccidentalsOffset,
|
||||
isNote,
|
||||
logger,
|
||||
noteToMidi,
|
||||
register,
|
||||
removeUndefineds,
|
||||
} from '@strudel/core';
|
||||
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
|
||||
|
||||
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
|
||||
|
|
@ -294,7 +303,7 @@ export const scale = register(
|
|||
}
|
||||
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
|
||||
} catch (err) {
|
||||
logger(`[tonal] ${err.message}`, 'error');
|
||||
errorLogger(err, 'tonal');
|
||||
return; // will be removed
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue