mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 05:05:26 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b8b0154da | |||
| 2b5c653c56 | |||
| 244d91cc8d | |||
| 3f368d6438 | |||
| 36ee4cfc98 |
@@ -0,0 +1,49 @@
|
|||||||
|
export const laserclock = ({ onTick, audioContext }) => {
|
||||||
|
const ready = new Promise((resolve) => {
|
||||||
|
const workletCode = `class LaserClock extends AudioWorkletProcessor {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.block = 0;
|
||||||
|
this.tick = 0;
|
||||||
|
this.started = false;
|
||||||
|
this.blocksPerCallback = 20;
|
||||||
|
this.port.onmessage = (e) => {
|
||||||
|
switch (e.data) {
|
||||||
|
case "start":
|
||||||
|
console.log('start!')
|
||||||
|
this.started = true;
|
||||||
|
break;
|
||||||
|
case "stop":
|
||||||
|
console.log('stop!')
|
||||||
|
this.started = false;
|
||||||
|
this.block = 0;
|
||||||
|
this.tick = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
process(inputs, outputs, parameters) {
|
||||||
|
if(!this.started) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(this.block % this.blocksPerCallback === 0) {
|
||||||
|
this.port.postMessage({tick: this.tick, currentTime});
|
||||||
|
this.tick++;
|
||||||
|
}
|
||||||
|
this.block++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerProcessor("laserclock-processor", LaserClock);`;
|
||||||
|
const dataURL = `data:text/javascript;base64,${btoa(workletCode)}`;
|
||||||
|
audioContext.audioWorklet.addModule(dataURL).then(() => {
|
||||||
|
const clock = new AudioWorkletNode(audioContext, 'laserclock-processor');
|
||||||
|
clock.port.onmessage = (e) => onTick(e.data);
|
||||||
|
clock.connect(audioContext.destination);
|
||||||
|
resolve(clock);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const start = () => ready.then((clock) => clock.port.postMessage('start'));
|
||||||
|
const stop = () => ready.then((clock) => clock.port.postMessage('stop'));
|
||||||
|
return { ready, start, stop };
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<body>
|
||||||
|
<button id="playButton">play</button>
|
||||||
|
<button id="stopButton">stop</button>
|
||||||
|
<script>
|
||||||
|
import { laserclock } from '../laserclock.mjs';
|
||||||
|
import { evaluate } from '@strudel/transpiler';
|
||||||
|
import { evalScope } from '@strudel/core';
|
||||||
|
import { superdough, getAudioContext, registerSynthSounds, initAudioOnFirstClick } from '@strudel/webaudio';
|
||||||
|
const init = async () => {
|
||||||
|
initAudioOnFirstClick();
|
||||||
|
await evalScope(
|
||||||
|
import('@strudel/core'),
|
||||||
|
import('@strudel/mini'),
|
||||||
|
import('@strudel/tonal'),
|
||||||
|
import('@strudel/webaudio'),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
init().then(async () => {
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
const code = `
|
||||||
|
// "tupper class" @by eddyflux
|
||||||
|
|
||||||
|
samples('github:yaxu/clean-breaks')
|
||||||
|
samples('github:eddyflux/crate')
|
||||||
|
samples('github:eddyflux/wax')
|
||||||
|
|
||||||
|
let cps = 68/60/4;
|
||||||
|
// setcps(cps) // currently not supported by lasterclock.. still sounds funky :D
|
||||||
|
|
||||||
|
stack(
|
||||||
|
s("loop4/4").fit()
|
||||||
|
.layer(
|
||||||
|
x=>x
|
||||||
|
.chop("64 [32 64]")
|
||||||
|
.juxBy(.6, rev)
|
||||||
|
.hpf(500)
|
||||||
|
.dec(sine.range(.05,.18).slow(8))
|
||||||
|
.room(.5).sometimes(mul(speed("2 | -2")))
|
||||||
|
.shape(.6).postgain(sine.range(0,.8).slow(16))
|
||||||
|
//.hush()
|
||||||
|
,
|
||||||
|
x=>x.chop(64).lpf(400)
|
||||||
|
.shape(.6).cut(4)
|
||||||
|
.dec(.2).postgain(.9)
|
||||||
|
.mask("<0!4 1!16>")
|
||||||
|
)
|
||||||
|
//.hush()
|
||||||
|
//.hpf(500)
|
||||||
|
,
|
||||||
|
s("riffin").fit().shape(.2).chop(32).dec(.1).postgain(.6)
|
||||||
|
.sometimesBy("0 [0 .5] 0 0", x=>x.set.squeeze(gain(".9 .6 .2 .1")).room(.3))
|
||||||
|
.sometimesBy("0 [0 0] 0 .5", x=>x.mul(speed("-2")))
|
||||||
|
.hurry("<.5!4 1!16>")
|
||||||
|
//.hush()
|
||||||
|
,
|
||||||
|
s("useme/2").fit()
|
||||||
|
.chop("<16 32>/32")//.rarely(ply("2"))
|
||||||
|
.sometimesBy("0 [.2 0]",ply("3"))
|
||||||
|
.dec(.19).room(.4).cut(9)
|
||||||
|
.hpf(800)
|
||||||
|
.mask("[1!4 0 1 1 0]*2")
|
||||||
|
//.hush()
|
||||||
|
,
|
||||||
|
s("[bd rim:1 [~ bd] rim:2]*2").bank('crate')
|
||||||
|
)
|
||||||
|
.reset("<x@3 x*[4 [8 [16 32]]] x@16>")
|
||||||
|
.late("[0 .002]*16").fast(cps)
|
||||||
|
`;
|
||||||
|
let { pattern } = await evaluate(code);
|
||||||
|
let last;
|
||||||
|
let minLatency = 0.1;
|
||||||
|
const { start, stop } = laserclock({
|
||||||
|
audioContext: ctx,
|
||||||
|
onTick: (data) => {
|
||||||
|
const { currentTime } = data;
|
||||||
|
if (last) {
|
||||||
|
const haps = pattern.queryArc(last, currentTime).filter((h) => h.hasOnset());
|
||||||
|
haps.forEach((hap) => {
|
||||||
|
const start = hap.whole.begin + minLatency;
|
||||||
|
superdough(hap.value, `=${start}`, hap.duration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
last = currentTime;
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
registerSynthSounds();
|
||||||
|
|
||||||
|
document.getElementById('playButton')?.addEventListener('click', () => start());
|
||||||
|
document.getElementById('stopButton')?.addEventListener('click', () => stop());
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
Reference in New Issue
Block a user