mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 13:13:10 -04:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d92d93e37d | |||
| 685937911c | |||
| 31a164a09f | |||
| be8b4d7873 | |||
| 60388e5798 | |||
| 88823d26b1 | |||
| e4099946cd | |||
| 3a49ba6eb1 | |||
| eac45d06b3 | |||
| 8674b61c2c | |||
| af27364c45 |
@@ -11,6 +11,7 @@ export class Cyclist {
|
|||||||
constructor({
|
constructor({
|
||||||
interval,
|
interval,
|
||||||
onTrigger,
|
onTrigger,
|
||||||
|
onPrepare,
|
||||||
onToggle,
|
onToggle,
|
||||||
onError,
|
onError,
|
||||||
getTime,
|
getTime,
|
||||||
@@ -18,6 +19,7 @@ export class Cyclist {
|
|||||||
setInterval,
|
setInterval,
|
||||||
clearInterval,
|
clearInterval,
|
||||||
beforeStart,
|
beforeStart,
|
||||||
|
prepareTime = 4,
|
||||||
}) {
|
}) {
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.beforeStart = beforeStart;
|
this.beforeStart = beforeStart;
|
||||||
@@ -26,6 +28,8 @@ export class Cyclist {
|
|||||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||||
this.lastBegin = 0; // query begin of last tick
|
this.lastBegin = 0; // query begin of last tick
|
||||||
this.lastEnd = 0; // query end of last tick
|
this.lastEnd = 0; // query end of last tick
|
||||||
|
this.preparedUntil = 0;
|
||||||
|
this.prepareTime = prepareTime;
|
||||||
this.getTime = getTime; // get absolute time
|
this.getTime = getTime; // get absolute time
|
||||||
this.num_cycles_at_cps_change = 0;
|
this.num_cycles_at_cps_change = 0;
|
||||||
this.seconds_at_cps_change; // clock phase when cps was changed
|
this.seconds_at_cps_change; // clock phase when cps was changed
|
||||||
@@ -85,6 +89,33 @@ export class Cyclist {
|
|||||||
setInterval,
|
setInterval,
|
||||||
clearInterval,
|
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() {
|
now() {
|
||||||
if (!this.started) {
|
if (!this.started) {
|
||||||
@@ -106,16 +137,19 @@ export class Cyclist {
|
|||||||
}
|
}
|
||||||
logger('[cyclist] start');
|
logger('[cyclist] start');
|
||||||
this.clock.start();
|
this.clock.start();
|
||||||
|
this.prepClock?.start();
|
||||||
this.setStarted(true);
|
this.setStarted(true);
|
||||||
}
|
}
|
||||||
pause() {
|
pause() {
|
||||||
logger('[cyclist] pause');
|
logger('[cyclist] pause');
|
||||||
this.clock.pause();
|
this.clock.pause();
|
||||||
|
this.prepClock?.pause();
|
||||||
this.setStarted(false);
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
logger('[cyclist] stop');
|
logger('[cyclist] stop');
|
||||||
this.clock.stop();
|
this.clock.stop();
|
||||||
|
this.prepClock?.stop();
|
||||||
this.lastEnd = 0;
|
this.lastEnd = 0;
|
||||||
this.setStarted(false);
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
@@ -130,6 +164,7 @@ export class Cyclist {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.cps = cps;
|
this.cps = cps;
|
||||||
|
this.preparedUntil = 0;
|
||||||
this.num_ticks_since_cps_change = 0;
|
this.num_ticks_since_cps_change = 0;
|
||||||
}
|
}
|
||||||
log(begin, end, haps) {
|
log(begin, end, haps) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
|
|||||||
|
|
||||||
export function repl({
|
export function repl({
|
||||||
defaultOutput,
|
defaultOutput,
|
||||||
|
defaultPrepare,
|
||||||
onEvalError,
|
onEvalError,
|
||||||
beforeEval,
|
beforeEval,
|
||||||
beforeStart,
|
beforeStart,
|
||||||
@@ -47,6 +48,7 @@ export function repl({
|
|||||||
|
|
||||||
const schedulerOptions = {
|
const schedulerOptions = {
|
||||||
onTrigger: getTrigger({ defaultOutput, getTime }),
|
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||||
|
onPrepare: getPrepare({ defaultPrepare }),
|
||||||
getTime,
|
getTime,
|
||||||
onToggle: (started) => {
|
onToggle: (started) => {
|
||||||
updateState({ started });
|
updateState({ started });
|
||||||
@@ -238,3 +240,13 @@ export const getTrigger =
|
|||||||
logger(`[cyclist] error: ${err.message}`, 'error');
|
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getPrepare =
|
||||||
|
({ defaultPrepare }) =>
|
||||||
|
async (hap) => {
|
||||||
|
try {
|
||||||
|
await defaultPrepare(hap);
|
||||||
|
} catch (err) {
|
||||||
|
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -92,6 +92,29 @@ superdough({ s: 'bd', delay: 0.5 }, 0, 1);
|
|||||||
- `deadline`: seconds until the sound should play (0 = immediate)
|
- `deadline`: seconds until the sound should play (0 = immediate)
|
||||||
- `duration`: seconds the sound should last. optional for one shot samples, required for synth sounds
|
- `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()
|
### registerSynthSounds()
|
||||||
|
|
||||||
Loads the default waveforms `sawtooth`, `square`, `triangle` and `sine`. Use them like this:
|
Loads the default waveforms `sawtooth`, `square`, `triangle` and `sine`. Use them like this:
|
||||||
|
|||||||
@@ -265,19 +265,28 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
|
|||||||
processSampleMap(
|
processSampleMap(
|
||||||
sampleMap,
|
sampleMap,
|
||||||
(key, bank) =>
|
(key, bank) =>
|
||||||
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), {
|
registerSound(
|
||||||
type: 'sample',
|
key,
|
||||||
samples: bank,
|
(t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank),
|
||||||
baseUrl,
|
{
|
||||||
prebake,
|
type: 'sample',
|
||||||
tag,
|
samples: bank,
|
||||||
}),
|
baseUrl,
|
||||||
|
prebake,
|
||||||
|
tag,
|
||||||
|
},
|
||||||
|
(hapValue) => onPrepareSample(hapValue, bank),
|
||||||
|
),
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cutGroups = [];
|
const cutGroups = [];
|
||||||
|
|
||||||
|
export async function onPrepareSample(hapValue, bank, resolveUrl) {
|
||||||
|
await getSampleBuffer(hapValue, bank, resolveUrl);
|
||||||
|
}
|
||||||
|
|
||||||
export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||||
let {
|
let {
|
||||||
s,
|
s,
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ export function setMultiChannelOrbits(bool) {
|
|||||||
|
|
||||||
export const soundMap = map();
|
export const soundMap = map();
|
||||||
|
|
||||||
export function registerSound(key, onTrigger, data = {}) {
|
export function registerSound(key, onTrigger, data = {}, onPrepare = () => {}) {
|
||||||
key = key.toLowerCase().replace(/\s+/g, '_');
|
key = key.toLowerCase().replace(/\s+/g, '_');
|
||||||
soundMap.setKey(key, { onTrigger, data });
|
soundMap.setKey(key, { onTrigger, data, onPrepare });
|
||||||
}
|
}
|
||||||
|
|
||||||
let gainCurveFunc = (val) => val;
|
let gainCurveFunc = (val) => val;
|
||||||
@@ -757,3 +757,13 @@ export const superdough = async (value, t, hapDuration, cps) => {
|
|||||||
export const superdoughTrigger = (t, hap, ct, cps) => {
|
export const superdoughTrigger = (t, hap, ct, cps) => {
|
||||||
superdough(hap, t - ct, hap.duration / cps, 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -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 * 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;
|
const { Pattern, logger, repl } = strudel;
|
||||||
|
|
||||||
setLogger(logger);
|
setLogger(logger);
|
||||||
@@ -20,6 +20,8 @@ export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(h
|
|||||||
export const webaudioOutput = (hap, deadline, hapDuration, cps, t) =>
|
export const webaudioOutput = (hap, deadline, hapDuration, cps, t) =>
|
||||||
superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration);
|
superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration);
|
||||||
|
|
||||||
|
export const webaudioPrepare = (hap) => prepare(hap2value(hap));
|
||||||
|
|
||||||
Pattern.prototype.webaudio = function () {
|
Pattern.prototype.webaudio = function () {
|
||||||
return this.onTrigger(webaudioOutputTrigger);
|
return this.onTrigger(webaudioOutputTrigger);
|
||||||
};
|
};
|
||||||
@@ -28,6 +30,7 @@ export function webaudioRepl(options = {}) {
|
|||||||
options = {
|
options = {
|
||||||
getTime: () => getAudioContext().currentTime,
|
getTime: () => getAudioContext().currentTime,
|
||||||
defaultOutput: webaudioOutput,
|
defaultOutput: webaudioOutput,
|
||||||
|
defaultPrepare: webaudioPrepare,
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
return repl(options);
|
return repl(options);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { transpiler } from '@strudel/transpiler';
|
|||||||
import {
|
import {
|
||||||
getAudioContextCurrentTime,
|
getAudioContextCurrentTime,
|
||||||
webaudioOutput,
|
webaudioOutput,
|
||||||
|
webaudioPrepare,
|
||||||
resetGlobalEffects,
|
resetGlobalEffects,
|
||||||
resetLoadedSounds,
|
resetLoadedSounds,
|
||||||
initAudioOnFirstClick,
|
initAudioOnFirstClick,
|
||||||
@@ -63,6 +64,7 @@ export function useReplContext() {
|
|||||||
const { isSyncEnabled, audioEngineTarget } = useSettings();
|
const { isSyncEnabled, audioEngineTarget } = useSettings();
|
||||||
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
|
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
|
||||||
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
|
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
|
||||||
|
const defaultPrepare = shouldUseWebaudio ? webaudioPrepare : undefined;
|
||||||
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
|
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
|
||||||
|
|
||||||
const init = useCallback(() => {
|
const init = useCallback(() => {
|
||||||
@@ -71,6 +73,7 @@ export function useReplContext() {
|
|||||||
const editor = new StrudelMirror({
|
const editor = new StrudelMirror({
|
||||||
sync: isSyncEnabled,
|
sync: isSyncEnabled,
|
||||||
defaultOutput,
|
defaultOutput,
|
||||||
|
defaultPrepare,
|
||||||
getTime,
|
getTime,
|
||||||
setInterval,
|
setInterval,
|
||||||
clearInterval,
|
clearInterval,
|
||||||
|
|||||||
Reference in New Issue
Block a user