mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 05:05:26 -04:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d92d93e37d | |||
| b312ff63a9 | |||
| 93bc353b6b | |||
| 14a0dea826 | |||
| 009f5445be | |||
| 3d37c73103 | |||
| 0b0f9fe13b | |||
| 1d189763b2 | |||
| a4b55776d2 | |||
| 0287be547a | |||
| 6a83ede33d | |||
| 1e3f546918 | |||
| faa60be211 | |||
| dde2b9c6f8 | |||
| 382dce65df | |||
| 63f4f202f1 | |||
| 366637026b | |||
| 608dfd515e | |||
| 685937911c | |||
| 31a164a09f | |||
| be8b4d7873 | |||
| 5e4eb7fc53 | |||
| 45bccf13ff | |||
| 65f1760fad | |||
| b8d2d02466 | |||
| 058cb36640 | |||
| 60388e5798 | |||
| 88823d26b1 | |||
| e4099946cd | |||
| 3a49ba6eb1 | |||
| eac45d06b3 | |||
| 8674b61c2c | |||
| af27364c45 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -445,6 +445,32 @@ export const { coarse } = registerControl('coarse');
|
||||
*/
|
||||
export const { drive } = registerControl('drive');
|
||||
|
||||
/**
|
||||
* Create byte beats with custom expressions
|
||||
*
|
||||
* @name byteBeatExpression
|
||||
* @synonyms bbexpr
|
||||
*
|
||||
* @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat
|
||||
* @example
|
||||
* s("bytebeat").bbexpr('t*(t>>15^t>>66)')
|
||||
*
|
||||
*/
|
||||
export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr');
|
||||
|
||||
/**
|
||||
* Create byte beats with custom expressions
|
||||
*
|
||||
* @name byteBeatStartTime
|
||||
* @synonyms bbst
|
||||
*
|
||||
* @param {number | Pattern} byteBeatStartTime in samples (t)
|
||||
* @example
|
||||
* note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope()
|
||||
*
|
||||
*/
|
||||
export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst');
|
||||
|
||||
/**
|
||||
* Allows you to set the output channels on the interface
|
||||
*
|
||||
|
||||
@@ -11,6 +11,7 @@ export class Cyclist {
|
||||
constructor({
|
||||
interval,
|
||||
onTrigger,
|
||||
onPrepare,
|
||||
onToggle,
|
||||
onError,
|
||||
getTime,
|
||||
@@ -18,6 +19,7 @@ export class Cyclist {
|
||||
setInterval,
|
||||
clearInterval,
|
||||
beforeStart,
|
||||
prepareTime = 4,
|
||||
}) {
|
||||
this.started = false;
|
||||
this.beforeStart = beforeStart;
|
||||
@@ -26,6 +28,8 @@ export class Cyclist {
|
||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||
this.lastBegin = 0; // query begin of last tick
|
||||
this.lastEnd = 0; // query end of last tick
|
||||
this.preparedUntil = 0;
|
||||
this.prepareTime = prepareTime;
|
||||
this.getTime = getTime; // get absolute time
|
||||
this.num_cycles_at_cps_change = 0;
|
||||
this.seconds_at_cps_change; // clock phase when cps was changed
|
||||
@@ -85,6 +89,33 @@ export class Cyclist {
|
||||
setInterval,
|
||||
clearInterval,
|
||||
);
|
||||
|
||||
onPrepare
|
||||
? (this.prepClock = createClock(
|
||||
getTime,
|
||||
(phase, duration, _, t) => {
|
||||
try {
|
||||
const start = Math.max(t, this.preparedUntil);
|
||||
const end = t + this.prepareTime;
|
||||
this.preparedUntil = end;
|
||||
|
||||
const haps = this.pattern.queryArc(start, end, { _cps: 1 });
|
||||
|
||||
haps.forEach((hap) => {
|
||||
onPrepare?.(hap);
|
||||
});
|
||||
} catch (e) {
|
||||
logger(`[cyclist] error: ${e.message}`);
|
||||
onError?.(e);
|
||||
}
|
||||
},
|
||||
1, // duration of each cycle
|
||||
1,
|
||||
0,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
))
|
||||
: null;
|
||||
}
|
||||
now() {
|
||||
if (!this.started) {
|
||||
@@ -106,16 +137,19 @@ export class Cyclist {
|
||||
}
|
||||
logger('[cyclist] start');
|
||||
this.clock.start();
|
||||
this.prepClock?.start();
|
||||
this.setStarted(true);
|
||||
}
|
||||
pause() {
|
||||
logger('[cyclist] pause');
|
||||
this.clock.pause();
|
||||
this.prepClock?.pause();
|
||||
this.setStarted(false);
|
||||
}
|
||||
stop() {
|
||||
logger('[cyclist] stop');
|
||||
this.clock.stop();
|
||||
this.prepClock?.stop();
|
||||
this.lastEnd = 0;
|
||||
this.setStarted(false);
|
||||
}
|
||||
@@ -130,6 +164,7 @@ export class Cyclist {
|
||||
return;
|
||||
}
|
||||
this.cps = cps;
|
||||
this.preparedUntil = 0;
|
||||
this.num_ticks_since_cps_change = 0;
|
||||
}
|
||||
log(begin, end, haps) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/core",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -2441,9 +2441,14 @@ const _chunk = function (n, func, pat, back = false, fast = false) {
|
||||
return pat.when(binary_pat, func);
|
||||
};
|
||||
|
||||
export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, false);
|
||||
});
|
||||
export const { chunk, slowchunk, slowChunk } = register(
|
||||
['chunk', 'slowchunk', 'slowChunk'],
|
||||
function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, false);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
|
||||
@@ -2455,9 +2460,14 @@ export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', '
|
||||
* "0 1 2 3".chunkBack(4, x=>x.add(7))
|
||||
* .scale("A:minor").note()
|
||||
*/
|
||||
export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) {
|
||||
return _chunk(n, func, pat, true);
|
||||
});
|
||||
export const { chunkBack, chunkback } = register(
|
||||
['chunkBack', 'chunkback'],
|
||||
function (n, func, pat) {
|
||||
return _chunk(n, func, pat, true);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Like `chunk`, but the cycles of the source pattern aren't repeated
|
||||
@@ -2471,9 +2481,14 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
|
||||
* .fastChunk(4, x => x.color('red')).slow(2)
|
||||
* .scale("C2:major").note()
|
||||
*/
|
||||
export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, true);
|
||||
});
|
||||
export const { fastchunk, fastChunk } = register(
|
||||
['fastchunk', 'fastChunk'],
|
||||
function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, true);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
// TODO - redefine elsewhere in terms of mask
|
||||
export const bypass = register(
|
||||
|
||||
@@ -8,6 +8,7 @@ import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
|
||||
|
||||
export function repl({
|
||||
defaultOutput,
|
||||
defaultPrepare,
|
||||
onEvalError,
|
||||
beforeEval,
|
||||
beforeStart,
|
||||
@@ -47,6 +48,7 @@ export function repl({
|
||||
|
||||
const schedulerOptions = {
|
||||
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||
onPrepare: getPrepare({ defaultPrepare }),
|
||||
getTime,
|
||||
onToggle: (started) => {
|
||||
updateState({ started });
|
||||
@@ -238,3 +240,13 @@ export const getTrigger =
|
||||
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
export const getPrepare =
|
||||
({ defaultPrepare }) =>
|
||||
async (hap) => {
|
||||
try {
|
||||
await defaultPrepare(hap);
|
||||
} catch (err) {
|
||||
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/csound",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/draw",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Helpers for drawing with Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/gamepad",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Gamepad Inputs for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/hydra",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Hydra integration for strudel",
|
||||
"main": "hydra.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/midi",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mini",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/motion",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "DeviceMotion API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mqtt",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "MQTT API for strudel",
|
||||
"main": "mqtt.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/osc",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "OSC messaging for strudel",
|
||||
"main": "osc.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/repl",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "Strudel REPL as a Web Component",
|
||||
"module": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/serial",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Webserial API for strudel",
|
||||
"main": "serial.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/soundfonts",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "Soundsfont support for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -92,6 +92,29 @@ superdough({ s: 'bd', delay: 0.5 }, 0, 1);
|
||||
- `deadline`: seconds until the sound should play (0 = immediate)
|
||||
- `duration`: seconds the sound should last. optional for one shot samples, required for synth sounds
|
||||
|
||||
### prepare(value)
|
||||
|
||||
Informs superdough that a sound will be needed in the future.
|
||||
If the sound is a sample that is not loaded yet, it will be fetched.
|
||||
Otherwise does nothing.
|
||||
`value` has a syntax identical to the one used `superdough()`.
|
||||
|
||||
```js
|
||||
prepare({ s: 'bd', delay: 0.5 });
|
||||
|
||||
// some time later
|
||||
|
||||
superdough({ s: 'bd', delay: 0.5 }, 0, 1);
|
||||
```
|
||||
|
||||
Can be awaited to ensure that a given sound is ready to play.
|
||||
|
||||
```js
|
||||
const sound = { s: 'hh' };
|
||||
await prepare(sound);
|
||||
superdough(sound, 0, 1);
|
||||
```
|
||||
|
||||
### registerSynthSounds()
|
||||
|
||||
Loads the default waveforms `sawtooth`, `square`, `triangle` and `sine`. Use them like this:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superdough",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -265,19 +265,28 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
|
||||
processSampleMap(
|
||||
sampleMap,
|
||||
(key, bank) =>
|
||||
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), {
|
||||
type: 'sample',
|
||||
samples: bank,
|
||||
baseUrl,
|
||||
prebake,
|
||||
tag,
|
||||
}),
|
||||
registerSound(
|
||||
key,
|
||||
(t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank),
|
||||
{
|
||||
type: 'sample',
|
||||
samples: bank,
|
||||
baseUrl,
|
||||
prebake,
|
||||
tag,
|
||||
},
|
||||
(hapValue) => onPrepareSample(hapValue, bank),
|
||||
),
|
||||
baseUrl,
|
||||
);
|
||||
};
|
||||
|
||||
const cutGroups = [];
|
||||
|
||||
export async function onPrepareSample(hapValue, bank, resolveUrl) {
|
||||
await getSampleBuffer(hapValue, bank, resolveUrl);
|
||||
}
|
||||
|
||||
export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||
let {
|
||||
s,
|
||||
|
||||
@@ -30,9 +30,9 @@ export function setMultiChannelOrbits(bool) {
|
||||
|
||||
export const soundMap = map();
|
||||
|
||||
export function registerSound(key, onTrigger, data = {}) {
|
||||
export function registerSound(key, onTrigger, data = {}, onPrepare = () => {}) {
|
||||
key = key.toLowerCase().replace(/\s+/g, '_');
|
||||
soundMap.setKey(key, { onTrigger, data });
|
||||
soundMap.setKey(key, { onTrigger, data, onPrepare });
|
||||
}
|
||||
|
||||
let gainCurveFunc = (val) => val;
|
||||
@@ -137,6 +137,7 @@ const defaultDefaultValues = {
|
||||
shapevol: 1,
|
||||
distortvol: 1,
|
||||
delay: 0,
|
||||
byteBeatExpression: '0',
|
||||
delayfeedback: 0.5,
|
||||
delaytime: 0.25,
|
||||
orbit: 1,
|
||||
@@ -445,7 +446,7 @@ function mapChannelNumbers(channels) {
|
||||
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||
}
|
||||
|
||||
export const superdough = async (value, t, hapDuration) => {
|
||||
export const superdough = async (value, t, hapDuration, cps) => {
|
||||
const ac = getAudioContext();
|
||||
t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
|
||||
let { stretch } = value;
|
||||
@@ -578,7 +579,7 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
// get source AudioNode
|
||||
let sourceNode;
|
||||
if (source) {
|
||||
sourceNode = source(t, value, hapDuration);
|
||||
sourceNode = source(t, value, hapDuration, cps);
|
||||
} else if (getSound(s)) {
|
||||
const { onTrigger } = getSound(s);
|
||||
const onEnded = () => {
|
||||
@@ -756,3 +757,13 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
export const superdoughTrigger = (t, hap, ct, cps) => {
|
||||
superdough(hap, t - ct, hap.duration / cps, cps);
|
||||
};
|
||||
|
||||
export const prepare = async (value) => {
|
||||
const { onPrepare } = getSound(value.s);
|
||||
if (onPrepare) {
|
||||
if (value.bank && value.s) {
|
||||
value.s = `${value.bank}_${value.s}`;
|
||||
}
|
||||
await onPrepare(value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,6 +144,82 @@ export function registerSynthSounds() {
|
||||
{ prebake: true, type: 'synth' },
|
||||
);
|
||||
|
||||
registerSound(
|
||||
'bytebeat',
|
||||
(begin, value, onended) => {
|
||||
const defaultBeats = [
|
||||
'(t%255 >= t/255%255)*255',
|
||||
'(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400',
|
||||
't',
|
||||
't*(t >> 10^t)',
|
||||
't&128',
|
||||
't&t>>8',
|
||||
'((t%255+t%128+t%64+t%32+t%16+t%127.8+t%64.8+t%32.8+t%16.8)/3)',
|
||||
'((t%64+t%63.8+t%64.15+t%64.35+t%63.5)/1.25)',
|
||||
'(t&(t>>7)-t)',
|
||||
'(sin(t*PI/128)*127+127)',
|
||||
'((t^t/2+t+64*(sin((t*PI/64)+(t*PI/32768))+64))%128*2)',
|
||||
'((t^t/2+t+64*(cos >> 0))%127.85*2)',
|
||||
'((t^t/2+t+64)%128*2)',
|
||||
'(((t * .25)^(t * .25)/100+(t * .25))%128)*2',
|
||||
'((t^t/2+t+64)%7 * 24)',
|
||||
];
|
||||
const { n = 0 } = value;
|
||||
const frequency = getFrequencyFromValue(value);
|
||||
const { byteBeatExpression = defaultBeats[n % defaultBeats.length], byteBeatStartTime } = value;
|
||||
|
||||
const ac = getAudioContext();
|
||||
|
||||
let { duration } = value;
|
||||
const [attack, decay, sustain, release] = getADSRValues(
|
||||
[value.attack, value.decay, value.sustain, value.release],
|
||||
'linear',
|
||||
[0.001, 0.05, 0.6, 0.01],
|
||||
);
|
||||
const holdend = begin + duration;
|
||||
const end = holdend + release + 0.01;
|
||||
|
||||
let o = getWorklet(
|
||||
ac,
|
||||
'byte-beat-processor',
|
||||
{
|
||||
frequency,
|
||||
begin,
|
||||
end,
|
||||
},
|
||||
{
|
||||
outputChannelCount: [2],
|
||||
},
|
||||
);
|
||||
|
||||
o.port.postMessage({ codeText: byteBeatExpression, byteBeatStartTime, frequency });
|
||||
|
||||
let envGain = gainNode(1);
|
||||
envGain = o.connect(envGain);
|
||||
|
||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
|
||||
|
||||
let timeoutNode = webAudioTimeout(
|
||||
ac,
|
||||
() => {
|
||||
destroyAudioWorkletNode(o);
|
||||
envGain.disconnect();
|
||||
onended();
|
||||
},
|
||||
begin,
|
||||
end,
|
||||
);
|
||||
|
||||
return {
|
||||
node: envGain,
|
||||
stop: (time) => {
|
||||
timeoutNode.stop(time);
|
||||
},
|
||||
};
|
||||
},
|
||||
{ prebake: true, type: 'synth' },
|
||||
);
|
||||
|
||||
registerSound(
|
||||
'pulse',
|
||||
(begin, value, onended) => {
|
||||
|
||||
@@ -295,7 +295,7 @@ class LadderProcessor extends AudioWorkletProcessor {
|
||||
cutoff = (cutoff * 2 * _PI) / sampleRate;
|
||||
cutoff = cutoff > 1 ? 1 : cutoff;
|
||||
|
||||
const k = Math.min(8, resonance * 0.4);
|
||||
const k = Math.min(8, resonance * 0.13);
|
||||
// drive makeup * resonance volume loss makeup
|
||||
let makeupgain = (1 / drive) * Math.min(1.75, 1 + k);
|
||||
|
||||
@@ -761,3 +761,137 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
|
||||
registerProcessor('pulse-oscillator', PulseOscillatorProcessor);
|
||||
|
||||
/** BYTE BEATS */
|
||||
const chyx = {
|
||||
/*bit*/ bitC: function (x, y, z) {
|
||||
return x & y ? z : 0;
|
||||
},
|
||||
/*bit reverse*/ br: function (x, size = 8) {
|
||||
if (size > 32) {
|
||||
throw new Error('br() Size cannot be greater than 32');
|
||||
} else {
|
||||
let result = 0;
|
||||
for (let idx = 0; idx < size - 0; idx++) {
|
||||
result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
/*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) {
|
||||
return Math.sin(x / (128 / Math.PI));
|
||||
},
|
||||
/*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) {
|
||||
return Math.cos(x / (128 / Math.PI));
|
||||
},
|
||||
/*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) {
|
||||
return Math.tan(x / (128 / Math.PI));
|
||||
},
|
||||
/*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) {
|
||||
return X.test(t.toString(2));
|
||||
},
|
||||
};
|
||||
|
||||
// Create shortened Math functions
|
||||
let mathParams, byteBeatHelperFuncs;
|
||||
function getByteBeatFunc(codetext) {
|
||||
if ((mathParams || byteBeatHelperFuncs) == null) {
|
||||
mathParams = Object.getOwnPropertyNames(Math);
|
||||
byteBeatHelperFuncs = mathParams.map((k) => Math[k]);
|
||||
const chyxNames = Object.getOwnPropertyNames(chyx);
|
||||
const chyxFuncs = chyxNames.map((k) => chyx[k]);
|
||||
mathParams.push('int', 'window', ...chyxNames);
|
||||
byteBeatHelperFuncs.push(Math.floor, globalThis, ...chyxFuncs);
|
||||
}
|
||||
return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs);
|
||||
}
|
||||
|
||||
class ByteBeatProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.port.onmessage = (event) => {
|
||||
let { codeText } = event.data;
|
||||
const { byteBeatStartTime } = event.data;
|
||||
if (byteBeatStartTime != null) {
|
||||
this.t = 0;
|
||||
this.initialOffset = Math.floor(byteBeatStartTime);
|
||||
}
|
||||
|
||||
//Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important
|
||||
//Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%")))
|
||||
codeText = codeText
|
||||
.trim()
|
||||
.replace(
|
||||
/^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/,
|
||||
(match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')),
|
||||
);
|
||||
|
||||
this.func = getByteBeatFunc(codeText);
|
||||
};
|
||||
this.initialOffset = null;
|
||||
this.t = null;
|
||||
this.func = null;
|
||||
}
|
||||
|
||||
static get parameterDescriptors() {
|
||||
return [
|
||||
{
|
||||
name: 'begin',
|
||||
defaultValue: 0,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: 'frequency',
|
||||
defaultValue: 440,
|
||||
min: Number.EPSILON,
|
||||
},
|
||||
{
|
||||
name: 'detune',
|
||||
defaultValue: 0,
|
||||
min: Number.NEGATIVE_INFINITY,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
{
|
||||
name: 'end',
|
||||
defaultValue: 0,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
min: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
process(inputs, outputs, params) {
|
||||
if (this.disconnected) {
|
||||
return false;
|
||||
}
|
||||
if (currentTime <= params.begin[0]) {
|
||||
return true;
|
||||
}
|
||||
if (currentTime >= params.end[0]) {
|
||||
return false;
|
||||
}
|
||||
if (this.t == null) {
|
||||
this.t = params.begin[0] * sampleRate;
|
||||
}
|
||||
const output = outputs[0];
|
||||
for (let i = 0; i < output[0].length; i++) {
|
||||
const detune = getParamValue(i, params.detune);
|
||||
const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100);
|
||||
let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset;
|
||||
const funcValue = this.func(local_t);
|
||||
let signal = (funcValue & 255) / 127.5 - 1;
|
||||
const out = signal * 0.2;
|
||||
|
||||
for (let c = 0; c < output.length; c++) {
|
||||
//prevent speaker blowout via clipping if threshold exceeds
|
||||
output[c][i] = clamp(out, -0.4, 0.4);
|
||||
}
|
||||
this.t = this.t + 1;
|
||||
}
|
||||
|
||||
return true; // keep the audio processing going
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('byte-beat-processor', ByteBeatProcessor);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/tonal",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Tonal functions for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/transpiler",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/web",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
|
||||
"module": "web.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/webaudio",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.3",
|
||||
"description": "Web Audio helpers for Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import * as strudel from '@strudel/core';
|
||||
import { superdough, getAudioContext, setLogger, doughTrigger } from 'superdough';
|
||||
import { superdough, prepare, getAudioContext, setLogger, doughTrigger } from 'superdough';
|
||||
const { Pattern, logger, repl } = strudel;
|
||||
|
||||
setLogger(logger);
|
||||
@@ -20,6 +20,8 @@ export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(h
|
||||
export const webaudioOutput = (hap, deadline, hapDuration, cps, t) =>
|
||||
superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration);
|
||||
|
||||
export const webaudioPrepare = (hap) => prepare(hap2value(hap));
|
||||
|
||||
Pattern.prototype.webaudio = function () {
|
||||
return this.onTrigger(webaudioOutputTrigger);
|
||||
};
|
||||
@@ -28,6 +30,7 @@ export function webaudioRepl(options = {}) {
|
||||
options = {
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
defaultOutput: webaudioOutput,
|
||||
defaultPrepare: webaudioPrepare,
|
||||
...options,
|
||||
};
|
||||
return repl(options);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/xen",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Xenharmonic API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1516,6 +1516,52 @@ exports[`runs examples > example "brandBy" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "byteBeatExpression" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
"[ 1/1 → 2/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
"[ 2/1 → 3/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
"[ 3/1 → 4/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "byteBeatStartTime" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/8 → 1/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/4 → 3/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 3/8 → 1/2 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/2 → 5/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 5/8 → 3/4 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 3/4 → 7/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 7/8 → 1/1 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 1/1 → 9/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 9/8 → 5/4 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 5/4 → 11/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 11/8 → 3/2 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 3/2 → 13/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 13/8 → 7/4 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 7/4 → 15/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 15/8 → 2/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 2/1 → 17/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 17/8 → 9/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 9/4 → 19/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 19/8 → 5/2 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 5/2 → 21/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 21/8 → 11/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 11/4 → 23/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 23/8 → 3/1 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
|
||||
"[ 3/1 → 25/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 25/8 → 13/4 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 13/4 → 27/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 27/8 → 7/2 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 7/2 → 29/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 29/8 → 15/4 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 15/4 → 31/8 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
"[ 31/8 → 4/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "cat" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:e5 ]",
|
||||
@@ -8022,56 +8068,6 @@ exports[`runs examples > example "scope" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "scramble
|
||||
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
|
||||
but parts might be played more than once, or not at all, per cycle." example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c s:piano ]",
|
||||
"[ 1/4 → 1/2 | note:d s:piano ]",
|
||||
"[ 1/2 → 3/4 | note:d s:piano ]",
|
||||
"[ 3/4 → 1/1 | note:c s:piano ]",
|
||||
"[ 1/1 → 5/4 | note:e s:piano ]",
|
||||
"[ 5/4 → 3/2 | note:e s:piano ]",
|
||||
"[ 3/2 → 7/4 | note:e s:piano ]",
|
||||
"[ 7/4 → 2/1 | note:c s:piano ]",
|
||||
"[ 2/1 → 9/4 | note:f s:piano ]",
|
||||
"[ 9/4 → 5/2 | note:d s:piano ]",
|
||||
"[ 5/2 → 11/4 | note:d s:piano ]",
|
||||
"[ 11/4 → 3/1 | note:e s:piano ]",
|
||||
"[ 3/1 → 13/4 | note:c s:piano ]",
|
||||
"[ 13/4 → 7/2 | note:f s:piano ]",
|
||||
"[ 7/2 → 15/4 | note:d s:piano ]",
|
||||
"[ 15/4 → 4/1 | note:f s:piano ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "scramble
|
||||
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
|
||||
but parts might be played more than once, or not at all, per cycle." example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:c s:piano ]",
|
||||
"[ 1/8 → 1/4 | note:d s:piano ]",
|
||||
"[ 1/4 → 3/8 | note:d s:piano ]",
|
||||
"[ 3/8 → 1/2 | note:c s:piano ]",
|
||||
"[ 1/2 → 1/1 | note:g s:piano ]",
|
||||
"[ 1/1 → 9/8 | note:e s:piano ]",
|
||||
"[ 9/8 → 5/4 | note:e s:piano ]",
|
||||
"[ 5/4 → 11/8 | note:e s:piano ]",
|
||||
"[ 11/8 → 3/2 | note:c s:piano ]",
|
||||
"[ 3/2 → 2/1 | note:g s:piano ]",
|
||||
"[ 2/1 → 17/8 | note:f s:piano ]",
|
||||
"[ 17/8 → 9/4 | note:d s:piano ]",
|
||||
"[ 9/4 → 19/8 | note:d s:piano ]",
|
||||
"[ 19/8 → 5/2 | note:e s:piano ]",
|
||||
"[ 5/2 → 3/1 | note:g s:piano ]",
|
||||
"[ 3/1 → 25/8 | note:c s:piano ]",
|
||||
"[ 25/8 → 13/4 | note:f s:piano ]",
|
||||
"[ 13/4 → 27/8 | note:d s:piano ]",
|
||||
"[ 27/8 → 7/2 | note:f s:piano ]",
|
||||
"[ 7/2 → 4/1 | note:g s:piano ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "scramble" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c s:piano ]",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { transpiler } from '@strudel/transpiler';
|
||||
import {
|
||||
getAudioContextCurrentTime,
|
||||
webaudioOutput,
|
||||
webaudioPrepare,
|
||||
resetGlobalEffects,
|
||||
resetLoadedSounds,
|
||||
initAudioOnFirstClick,
|
||||
@@ -63,6 +64,7 @@ export function useReplContext() {
|
||||
const { isSyncEnabled, audioEngineTarget } = useSettings();
|
||||
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
|
||||
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
|
||||
const defaultPrepare = shouldUseWebaudio ? webaudioPrepare : undefined;
|
||||
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
|
||||
|
||||
const init = useCallback(() => {
|
||||
@@ -71,6 +73,7 @@ export function useReplContext() {
|
||||
const editor = new StrudelMirror({
|
||||
sync: isSyncEnabled,
|
||||
defaultOutput,
|
||||
defaultPrepare,
|
||||
getTime,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
|
||||
Reference in New Issue
Block a user