mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-31 00:34:25 -04:00
add more web audio stuff
This commit is contained in:
@@ -10,8 +10,9 @@
|
||||
Loading...</textarea
|
||||
>
|
||||
<script type="module">
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
|
||||
document.body.style = 'margin: 0';
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
|
||||
import 'https://cdn.skypack.dev/@strudel.cycles/core@0.0.2/euclid.mjs';
|
||||
const { cat, State, TimeSpan } = strudel;
|
||||
let pattern;
|
||||
Object.assign(window, strudel); // add strudel to eval scope
|
||||
@@ -39,7 +40,6 @@ Loading...</textarea
|
||||
}
|
||||
};
|
||||
input.addEventListener('input', () => evaluate());
|
||||
evaluate(); // evaluate initial code
|
||||
|
||||
// helpers to create a worker dynamically without needing a server / extra file
|
||||
const stringifyFunction = (func) => '(' + func + ')();';
|
||||
@@ -100,34 +100,108 @@ Loading...</textarea
|
||||
const gainNode = audioContext.createGain();
|
||||
gainNode.gain.setValueAtTime(0, begin);
|
||||
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
|
||||
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain
|
||||
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain start
|
||||
gainNode.gain.setValueAtTime(sustain * velocity, end); // sustain end
|
||||
gainNode.gain.linearRampToValueAtTime(0, end + release); // release
|
||||
// for some reason, using exponential ramping creates little cracklings
|
||||
return gainNode;
|
||||
};
|
||||
|
||||
strudel.Pattern.prototype.withAudioNode = function (createAudioNode) {
|
||||
return this._withEvent((event) => {
|
||||
return event.setContext({
|
||||
...event.context,
|
||||
createAudioNode: (e) => createAudioNode(e, event.context.createAudioNode?.(event)),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
strudel.Pattern.prototype._osc = function (type) {
|
||||
return this.withAudioNode((e) => {
|
||||
const osc = audioContext.createOscillator();
|
||||
osc.type = type;
|
||||
osc.frequency.value = e.value; // expects frequency..
|
||||
osc.start(e.whole.begin.valueOf());
|
||||
osc.stop(e.whole.end.valueOf()); // release?
|
||||
// osc.connect(audioContext.destination);
|
||||
return osc;
|
||||
});
|
||||
};
|
||||
strudel.Pattern.prototype.define('osc', (type, pat) => pat.osc(type), { patternified: true });
|
||||
strudel.Pattern.prototype.adsr = function (a = 0.01, d = 0.05, s = 1, r = 0.01) {
|
||||
return this.withAudioNode((e, node) => {
|
||||
const velocity = e.context?.velocity || 1;
|
||||
const envelope = adsr(a, d, s, r, velocity, e.whole.begin.valueOf(), e.whole.end.valueOf());
|
||||
node?.connect(envelope);
|
||||
return envelope;
|
||||
});
|
||||
};
|
||||
strudel.Pattern.prototype.filter = function (type = 'lowshelf', frequency = 1000, gain = 25) {
|
||||
return this.withAudioNode((e, node) => {
|
||||
const filter = audioContext.createBiquadFilter();
|
||||
filter.type = type;
|
||||
filter.frequency.value = frequency;
|
||||
filter.gain.value = gain;
|
||||
node?.connect(filter);
|
||||
return filter;
|
||||
});
|
||||
};
|
||||
|
||||
strudel.Pattern.prototype.out = function () {
|
||||
const master = audioContext.createGain();
|
||||
master.gain.value = 0.1;
|
||||
master.connect(audioContext.destination);
|
||||
return this.withAudioNode((e, node) => {
|
||||
if (!node) {
|
||||
console.warn('out: no source! call .osc() first');
|
||||
}
|
||||
node?.connect(master);
|
||||
});
|
||||
};
|
||||
|
||||
const audioContext = new AudioContext();
|
||||
const metro = new Metro(audioContext, (begin, end) => {
|
||||
pattern.query(new State(new TimeSpan(begin, end))).forEach((e) => {
|
||||
if (!e.part.begin.equals(e.whole.begin)) {
|
||||
return;
|
||||
}
|
||||
const attack = 0.01;
|
||||
const decay = 0.05;
|
||||
const sustain = 0.5;
|
||||
const release = 0.01;
|
||||
const velocity = (e.context?.velocity || 1) * 0.1;
|
||||
const osc = audioContext.createOscillator();
|
||||
osc.type = 'sine';
|
||||
osc.frequency.value = e.value;
|
||||
osc.start(e.whole.begin);
|
||||
const envelope = adsr(attack, decay, sustain, release, velocity, e.whole.begin, e.whole.end);
|
||||
osc.stop(e.whole.end + release);
|
||||
osc.connect(envelope);
|
||||
envelope.connect(audioContext.destination);
|
||||
if (e.context.createAudioNode) {
|
||||
e.context.createAudioNode(e);
|
||||
} else {
|
||||
// fallback sine wave
|
||||
const attack = 0.01;
|
||||
const decay = 0.05;
|
||||
const sustain = 0.5;
|
||||
const release = 0.01;
|
||||
const velocity = (e.context?.velocity || 1) * 0.1;
|
||||
const osc = audioContext.createOscillator();
|
||||
osc.type = 'sine';
|
||||
osc.frequency.value = e.value;
|
||||
osc.start(e.whole.begin);
|
||||
const envelope = adsr(attack, decay, sustain, release, velocity, e.whole.begin, e.whole.end);
|
||||
osc.stop(e.whole.end + release);
|
||||
osc.connect(envelope);
|
||||
envelope.connect(audioContext.destination);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('start').addEventListener('click', () => metro.start());
|
||||
document.getElementById('stop').addEventListener('click', () => metro.stop());
|
||||
evaluate(); // evaluate initial code
|
||||
/*
|
||||
sequence(1,3/2,1,2).stack(
|
||||
stack(5,3,4).velocity(.8).slow(2).legato(.2)
|
||||
//.echo(3, 1/4, .5)
|
||||
).mul(110) // frequencies
|
||||
.div(slowcat(slowcat(2,5/4, 3/2),slowcat(3,4/3)).slow(2))
|
||||
.echoWith(
|
||||
16, // n of partials / cutoff
|
||||
tri2.slow(128).div(2), // time between partials
|
||||
(x,n)=>x // n = partial index 0..n
|
||||
.mul((n+1)*3/2)
|
||||
.legato(2)
|
||||
.velocity(1/((n+1)**1.6)) // amplitude falloff
|
||||
).legato(2) // note length
|
||||
*/
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user