mirror of
https://codeberg.org/uzu/strudel
synced 2026-10-05 17:03:34 -04:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7e88e1ff2 | |||
| f6a43f1b10 | |||
| a3b183d304 | |||
| f7175c0505 | |||
| e26040d154 | |||
| 17a1b75e5b | |||
| 7df9688bb9 | |||
| d557bb59f0 | |||
| 679e81eb0f | |||
| 37dd834398 | |||
| 959e8ac7bc | |||
| 16524ca805 | |||
| a79491dd16 | |||
| 8529516c66 |
@@ -1,14 +1,17 @@
|
|||||||
# strudel
|
# strudel
|
||||||
|
|
||||||
Live coding patterns on the web
|
Live coding patterns on the web
|
||||||
https://strudel.cc/
|
|
||||||
|
|
||||||
- Try it here: <https://strudel.cc>
|
- Try it here: <https://strudel.cc>
|
||||||
- Docs: <https://strudel.cc/learn>
|
- Docs: <https://strudel.cc/learn>
|
||||||
|
- Source: https://codeberg.org/uzu/strudel/
|
||||||
|
* Along with many other live coding projects, we have moved from Microsoft's Github platform to Codeberg for ethical reasons. **Please don't fork the project back to github**.
|
||||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||||
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
|
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
|
||||||
- 2 Years of Strudel Blog Post: <https://strudel.cc/blog/#year-2>
|
- 2 Years of Strudel Blog Post: <https://strudel.cc/blog/#year-2>
|
||||||
|
|
||||||
|
|
||||||
## Running Locally
|
## Running Locally
|
||||||
|
|
||||||
After cloning the project, you can run the REPL locally:
|
After cloning the project, you can run the REPL locally:
|
||||||
|
|||||||
@@ -461,10 +461,11 @@ export function applyFM(param, value, begin) {
|
|||||||
nodes[`fm_${idx}`] = [osc];
|
nodes[`fm_${idx}`] = [osc];
|
||||||
}
|
}
|
||||||
const { input, output, freq, osc, toCleanup } = fms[idx];
|
const { input, output, freq, osc, toCleanup } = fms[idx];
|
||||||
const g = gainNode(amt * freq);
|
const gAmt = gainNode(amt);
|
||||||
io.push(isMod ? output.connect(g) : input);
|
const gFreq = gainNode(freq);
|
||||||
cleanupOnEnd(osc, [...toCleanup, g]);
|
io.push(isMod ? output.connect(gAmt).connect(gFreq) : input);
|
||||||
nodes[`fm_${idx}_gain`] = [g];
|
cleanupOnEnd(osc, [...toCleanup, gAmt, gFreq]);
|
||||||
|
nodes[`fm_${idx}_gain`] = [gAmt];
|
||||||
}
|
}
|
||||||
if (!io[1]) {
|
if (!io[1]) {
|
||||||
logger(
|
logger(
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ const getNodeParam = (node, name) => {
|
|||||||
|
|
||||||
const controlTargets = getSuperdoughControlTargets();
|
const controlTargets = getSuperdoughControlTargets();
|
||||||
|
|
||||||
const getControlData = (control) => {
|
const getControlData = (control, subControl) => {
|
||||||
return controlTargets[control.split('_')[0]];
|
const controlNoIdx = control.split('_')[0];
|
||||||
|
return controlTargets[`${controlNoIdx}_${subControl}`] ?? controlTargets[controlNoIdx];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRangeForParam = (paramName, currentValue) => {
|
const getRangeForParam = (paramName, currentValue) => {
|
||||||
@@ -59,8 +60,7 @@ const clampWithWaveShaper = (modulator, min, max) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getTargetParamsForControl = (control, nodes, subControl) => {
|
const getTargetParamsForControl = (control, nodes, subControl) => {
|
||||||
const lookupKey = subControl ? `${control}_${subControl}` : control;
|
const targetInfo = getControlData(control, subControl);
|
||||||
const targetInfo = getControlData(lookupKey) ?? getControlData(control);
|
|
||||||
if (!targetInfo) {
|
if (!targetInfo) {
|
||||||
errorLogger(
|
errorLogger(
|
||||||
new Error(`Could not find control data for target '${control}'. It may not be modulatable.`),
|
new Error(`Could not find control data for target '${control}'. It may not be modulatable.`),
|
||||||
|
|||||||
@@ -65,21 +65,22 @@ export const getSampleBufferSource = async (hapValue, bank, resolveUrl) => {
|
|||||||
bufferSource.playbackRate.value = playbackRate;
|
bufferSource.playbackRate.value = playbackRate;
|
||||||
|
|
||||||
const { loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue;
|
const { loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue;
|
||||||
|
const bufferDuration = bufferSource.buffer.duration;
|
||||||
|
|
||||||
// "The computation of the offset into the sound is performed using the sound buffer's natural sample rate,
|
// The computation of the offset into the sound is performed using the sound buffer's natural duration,
|
||||||
// rather than the current playback rate, so even if the sound is playing at twice its normal speed,
|
// rather than the playback duration, so that even if the sound is playing at twice its normal speed,
|
||||||
// the midway point through a 10-second audio buffer is still 5."
|
// the midway point through a 10-second audio buffer is still 5.
|
||||||
const offset = begin * bufferSource.buffer.duration;
|
const offset = begin * bufferDuration;
|
||||||
|
|
||||||
const loop = hapValue.loop;
|
const loop = hapValue.loop;
|
||||||
if (loop) {
|
if (loop) {
|
||||||
bufferSource.loop = true;
|
bufferSource.loop = true;
|
||||||
bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset;
|
bufferSource.loopStart = loopBegin * bufferDuration;
|
||||||
bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset;
|
bufferSource.loopEnd = loopEnd * bufferDuration;
|
||||||
}
|
}
|
||||||
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
const playbackDuration = bufferDuration / bufferSource.playbackRate.value;
|
||||||
const sliceDuration = (end - begin) * bufferDuration;
|
const sliceDuration = (end - begin) * playbackDuration;
|
||||||
return { bufferSource, offset, bufferDuration, sliceDuration };
|
return { bufferSource, offset, bufferDuration, playbackDuration, sliceDuration };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadBuffer = (url, ac, s, n = 0) => {
|
export const loadBuffer = (url, ac, s, n = 0) => {
|
||||||
|
|||||||
@@ -42,4 +42,18 @@ describe('transpiler', () => {
|
|||||||
[12, 14],
|
[12, 14],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
it('allows disabling mini', () => {
|
||||||
|
const code = `/* mini-off */
|
||||||
|
const randPrefix = Math.random() > 0.5 ? "b" : "s";
|
||||||
|
const drumPat = \`\${randPrefix}d\`;
|
||||||
|
// mini-on
|
||||||
|
s(drumPat).lpf("5000 10000") // make sure mini still runs;
|
||||||
|
`;
|
||||||
|
const { output, miniLocations } = transpiler(code, { ...simple, emitMiniLocations: true });
|
||||||
|
expect(output).not.toContain("m('b'");
|
||||||
|
expect(output).not.toContain("m('s'");
|
||||||
|
const cutoffIdx = code.indexOf('5000 10000');
|
||||||
|
expect(miniLocations).toHaveLength(2);
|
||||||
|
expect(miniLocations[0][0]).toEqual(cutoffIdx);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ export function registerLanguage(type, config) {
|
|||||||
export function transpiler(input, options = {}) {
|
export function transpiler(input, options = {}) {
|
||||||
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
|
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
|
||||||
|
|
||||||
|
const comments = [];
|
||||||
let ast = parse(input, {
|
let ast = parse(input, {
|
||||||
ecmaVersion: 2022,
|
ecmaVersion: 2022,
|
||||||
allowAwaitOutsideFunction: true,
|
allowAwaitOutsideFunction: true,
|
||||||
locations: true,
|
locations: true,
|
||||||
|
onComment: comments,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const miniDisableRanges = findMiniDisableRanges(comments, input.length);
|
||||||
let miniLocations = [];
|
let miniLocations = [];
|
||||||
const collectMiniLocations = (value, node) => {
|
const collectMiniLocations = (value, node) => {
|
||||||
const minilang = languages.get('minilang');
|
const minilang = languages.get('minilang');
|
||||||
@@ -66,6 +69,9 @@ export function transpiler(input, options = {}) {
|
|||||||
return this.replace(tidalWithLocation(raw, offset));
|
return this.replace(tidalWithLocation(raw, offset));
|
||||||
}
|
}
|
||||||
if (isBackTickString(node, parent)) {
|
if (isBackTickString(node, parent)) {
|
||||||
|
if (isMiniDisabled(node.start, miniDisableRanges)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { quasis } = node;
|
const { quasis } = node;
|
||||||
const { raw } = quasis[0].value;
|
const { raw } = quasis[0].value;
|
||||||
this.skip();
|
this.skip();
|
||||||
@@ -73,6 +79,9 @@ export function transpiler(input, options = {}) {
|
|||||||
return this.replace(miniWithLocation(raw, node));
|
return this.replace(miniWithLocation(raw, node));
|
||||||
}
|
}
|
||||||
if (isStringWithDoubleQuotes(node)) {
|
if (isStringWithDoubleQuotes(node)) {
|
||||||
|
if (isMiniDisabled(node.start, miniDisableRanges)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { value } = node;
|
const { value } = node;
|
||||||
this.skip();
|
this.skip();
|
||||||
emitMiniLocations && collectMiniLocations(value, node);
|
emitMiniLocations && collectMiniLocations(value, node);
|
||||||
@@ -327,3 +336,32 @@ function languageWithLocation(name, value, offset) {
|
|||||||
optional: false,
|
optional: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findMiniDisableRanges(comments, codeEnd) {
|
||||||
|
const ranges = [];
|
||||||
|
const stack = []; // used to track on/off pairs
|
||||||
|
for (const comment of comments) {
|
||||||
|
const value = comment.value.trim();
|
||||||
|
if (value.startsWith('mini-off')) {
|
||||||
|
stack.push(comment.start);
|
||||||
|
} else if (value.startsWith('mini-on')) {
|
||||||
|
const start = stack.pop();
|
||||||
|
ranges.push([start, comment.end]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (stack.length) {
|
||||||
|
// If no closing mini-on is found, just turn it off until `codeEnd`
|
||||||
|
const start = stack.pop();
|
||||||
|
ranges.push([start, codeEnd]);
|
||||||
|
}
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMiniDisabled(offset, miniDisableRanges) {
|
||||||
|
for (const [start, end] of miniDisableRanges) {
|
||||||
|
if (offset >= start && offset < end) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user