rewrite webaudio + migrate tunes + empty setters

This commit is contained in:
Felix Roos 2022-06-17 23:23:51 +02:00
parent f69d776cf6
commit 4c05876066
4 changed files with 125 additions and 160 deletions

View File

@ -750,19 +750,22 @@ const generic_params = [
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
const _setter = (func) =>
const _setter = (func, name) =>
function (...pats) {
if (!pats.length) {
return this.fmap((value) => ({ [name]: value }));
}
return this.set(func(...pats));
};
generic_params.forEach(([type, name, description]) => {
controls[name] = (...pats) => _name(name, ...pats);
Pattern.prototype[name] = _setter(controls[name]);
Pattern.prototype[name] = _setter(controls[name], name);
});
// create custom param
controls.createParam = (name) => {
Pattern.prototype[name] = _setter(controls[name]);
Pattern.prototype[name] = _setter(controls[name], name);
return (...pats) => _name(name, ...pats);
};

View File

@ -6,21 +6,20 @@ This program is free software: you can redistribute it and/or modify it under th
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core';
import { fromMidi } from '@strudel.cycles/core';
import { Tone } from '@strudel.cycles/tone';
const { Pattern, getFrequency, patternify2 } = strudel;
const { Pattern } = strudel;
let audioContext;
export const getAudioContext = () => {
if (!audioContext) {
audioContext = new AudioContext();
}
return audioContext;
const getFilter = (ac, type, frequency, Q) => {
const filter = ac.createBiquadFilter();
filter.type = type;
filter.frequency.value = frequency;
filter.Q.value = Q;
return filter;
};
const lookahead = 0.2;
const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
const gainNode = getAudioContext().createGain();
const getADSR = (ac, attack, decay, sustain, release, velocity, begin, end) => {
const gainNode = ac.createGain();
gainNode.gain.setValueAtTime(0, begin);
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain start
@ -30,129 +29,70 @@ const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
return gainNode;
};
Pattern.prototype.withAudioNode = function (createAudioNode) {
return this._withHap((hap) => {
return hap.setContext({
...hap.context,
createAudioNode: (t, e) => createAudioNode(t, e, hap.context.createAudioNode?.(t, hap)),
});
});
};
Pattern.prototype._wave = function (type) {
return this.withAudioNode((t, e) => {
const osc = getAudioContext().createOscillator();
osc.type = type;
const f = getFrequency(e);
osc.frequency.value = f; // expects frequency..
const begin = t ?? e.whole.begin.valueOf() + lookahead;
const end = begin + e.duration.valueOf();
osc.start(begin);
osc.stop(end); // release?
return osc;
});
};
Pattern.prototype.adsr = function (a = 0.01, d = 0.05, s = 1, r = 0.01) {
return this.withAudioNode((t, e, node) => {
const velocity = e.context?.velocity || 1;
const begin = t ?? e.whole.begin.valueOf() + lookahead;
const end = begin + e.duration.valueOf() + lookahead;
const envelope = adsr(a, d, s, r, velocity, begin, end);
node?.connect(envelope);
return envelope;
});
};
Pattern.prototype._filter = function (type = 'lowpass', frequency = 1000) {
return this.withAudioNode((t, e, node) => {
const filter = getAudioContext().createBiquadFilter();
filter.type = type;
filter.frequency.value = frequency;
node?.connect(filter);
return filter;
});
};
Pattern.prototype.filter = function (type, frequency) {
return patternify2(Pattern.prototype._filter)(reify(type), reify(frequency), this);
};
Pattern.prototype.out = function () {
const master = getAudioContext().createGain();
master.gain.value = 0.1;
master.connect(getAudioContext().destination);
return this.withAudioNode((t, e, node) => {
if (!node) {
console.warn('out: no source! call .osc() first');
return this.onTrigger((t, hap, ct) => {
const ac = Tone.getContext().rawContext;
// calculate correct time (tone.js workaround)
t = ac.currentTime + t - ct;
// destructure value
let {
freq,
s,
n,
gain = 1,
cutoff,
resonance = 1,
hcutoff,
hresonance = 1,
bandf,
bandq = 1,
pan,
attack = 0.001,
decay = 0,
sustain = 1,
release = 0.001,
} = hap.value;
if (!n && !freq) {
console.warn('unplayable value:', hap.value);
return;
}
node?.connect(master);
})._withHap((hap) => {
const onTrigger = (time, e) => e.context?.createAudioNode?.(time, e);
return hap.setContext({ ...hap.context, onTrigger });
// get frequency
if (!freq && typeof n === 'number') {
freq = fromMidi(n); // + 48);
}
if (!freq && typeof n === 'string') {
freq = fromMidi(toMidi(n));
}
// the chain will hold all audio nodes that connect to each other
const chain = [];
// make oscillator
const o = ac.createOscillator();
o.type = s || 'triangle';
o.frequency.value = Number(freq);
o.start(t);
o.stop(t + hap.duration + release);
chain.push(o);
// envelope
const adsr = getADSR(ac, attack, decay, sustain, release, 1, t, t + hap.duration);
chain.push(adsr);
// filters
cutoff !== undefined && chain.push(getFilter(ac, 'lowpass', cutoff, resonance));
hcutoff !== undefined && chain.push(getFilter(ac, 'highpass', hcutoff, hresonance));
bandf !== undefined && chain.push(getFilter(ac, 'bandpass', bandf, bandq));
// TODO vowel
// TODO delay / delaytime / delayfeedback
// panning
if (pan !== undefined) {
const panner = ac.createStereoPanner();
panner.pan.value = 2 * pan - 1;
chain.push(panner);
}
// master out
const master = ac.createGain();
master.gain.value = 0.1 * gain;
chain.push(master);
chain.push(ac.destination);
// connect chain elements together
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
});
};
Pattern.prototype.define('wave', (type, pat) => pat.wave(type), { patternified: true });
/*
// TODO: throw all this away and use that:
stack(
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)")
.layer(x=>x.mul("1.006,.995"))), // detune
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
).s("<sawtooth square>/2")
.cutoff(perlin.range(100,4000).slow(4))//.resonance(saw.range(0,15).slow(7))
.jux(rev)
.onTrigger((t,hap,ct) => {
const ac = Tone.getContext().rawContext;
// calculate correct time (tone.js workaround)
t = ac.currentTime + t - ct;
// destructure value
const { freq, s, gain = 1,
cutoff, resonance = 1,
hcutoff, hresonance = 1,
bandf, bandq = 1,
pan
} = hap.value;
// TODO get frequency from n or note
// oscillator
const o = ac.createOscillator();
o.type = s || 'triangle';
o.frequency.value = Number(freq);
o.start(t);
o.stop(t + hap.duration);
// chaining logic
let last = o;
const addToChain = (node) => {
last.connect(node);
last = node;
}
// filters
const getFilter = (type, frequency, Q) => {
const filter = ac.createBiquadFilter();
filter.type = type;
filter.frequency.value = frequency;
filter.Q.value = Q;
return filter;
}
cutoff !== undefined && addToChain(getFilter('lowpass', cutoff, resonance));
hcutoff !== undefined && addToChain(getFilter('highpass', hcutoff, hresonance));
bandf !== undefined && addToChain(getFilter('bandpass', bandf, bandq));
// TODO: vowel
// TODO: delay / delaytime / delayfeedback
// panning
if(pan !== undefined) {
const panner = ac.createStereoPanner();
panner.pan.value = 2*pan-1;
addToChain(panner);
}
// gain out
const master = ac.createGain();
master.gain.value = 0.1 * gain;
master.connect(ac.destination);
last.connect(master);
})
.stack(s("bd(3,8),hh*4,~ sd").webdirt())
*/

2
repl/.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
oldtunes.mjs

View File

@ -728,7 +728,7 @@ bell = bell.chain(vol(0.6).connect(delay),out());
.slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
export const waa = `"a4 [a3 c3] a3 c3"
/* export const waa = `n("a4 [a3 c3] a3 c3")
.sub("<7 12>/2")
.off(1/8, add("12"))
.off(1/4, add("7"))
@ -736,23 +736,37 @@ export const waa = `"a4 [a3 c3] a3 c3"
.slow(2)
.wave("sawtooth square")
.filter('lowpass', "<2000 1000 500>")
.out()`;
.out()`; */
export const waar = `"a4 [a3 c3] a3 c3".color('#F9D649')
.sub("<7 12 5 12>".slow(2))
.off(1/4,x=>x.add(7).color("#FFFFFF #0C3AA1 #C63928"))
.off(1/8,x=>x.add(12).color('#215CB6'))
.slow(2)
.legato(sine.range(0.3, 2).slow(28))
.wave("sawtooth square".fast(2))
.filter('lowpass', cosine.range(500,4000).slow(16))
.out()
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})`;
export const waa = `n(
"a4 [a3 c3] a3 c3"
.sub("<7 12>/2")
.off(1/8, add("12"))
.off(1/4, add("7"))
)
.legato(.5)
.slow(2)
.s("sawtooth square")
.cutoff("<2000 1000 500>")
.out()
`;
export const waa2 = `n(
"a4 [a3 c3] a3 c3"
.sub("<7 12 5 12>".slow(2))
.off(1/4,x=>x.add(7))
.off(1/8,x=>x.add(12))
)
.slow(2)
.legato(sine.range(0.3, 2).slow(28))
.s("sawtooth square".fast(2))
.cutoff(cosine.range(500,4000).slow(16))
.out()`;
export const hyperpop = `const lfo = cosine.slow(15);
const lfo2 = sine.slow(16);
const filter1 = x=>x.filter('lowpass', lfo2.range(300,3000));
const filter2 = x=>x.filter('highpass', lfo.range(1000,6000)).filter('lowpass',4000)
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
const filter2 = x=>x.hcutoff(lfo.range(1000,6000)).cutoff(4000)
const scales = cat('D3 major', 'G3 major').slow(8)
const drums = await players({
@ -765,24 +779,30 @@ const drums = await players({
stack(
"-7 0 -7 7".struct("x(5,8,2)").fast(2).sub(7)
.scale(scales).wave("sawtooth,square").velocity(.3).adsr(0.01,0.1,.5,0)
.scale(scales)
.n()
.s("sawtooth,square")
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
.apply(filter1),
"~@3 [<2 3>,<4 5>]"
.echo(8,1/16,.7)
.echo(4,1/16,.7)
.scale(scales)
.wave('square').velocity(.7).adsr(0.01,0.1,0).apply(filter1),
"6 5 4".add(14)
.n()
.s('square').gain(.7)
.attack(0.01).decay(0.1).sustain(0)
.apply(filter1),
"6 4 2".add(14)
.superimpose(sub("5"))
.fast(1).euclidLegato(3,8)
.mask("<1 0@7>")
.fast(2)
.echo(32, 1/8, .9)
.echo(32, 1/8, .8)
.scale(scales)
.wave("sawtooth")
.velocity(.2)
.adsr(.01,.5,0)
.n()
.s("sawtooth")
.gain(sine.range(.1,.4).slow(8))
.attack(.001).decay(.2).sustain(0)
.apply(filter2)
//.echo(4,1/16,.5)
).out().stack(
stack(
"bd <~@7 [~ bd]>".fast(2),
@ -790,7 +810,7 @@ stack(
"[~ hh3]*2"
).tone(drums.chain(vol(.18),out())).fast(2)
).slow(2)
//.pianoroll({minMidi:20, maxMidi:160})
// strudel disable-highlighting`;