mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-23 05:26:37 -04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 679e81eb0f | |||
| 37dd834398 | |||
| 959e8ac7bc | |||
| 16524ca805 | |||
| b4b0194455 | |||
| fd472a64d9 | |||
| 58094973b0 | |||
| db49b37780 | |||
| bf243c04b9 | |||
| bc05cffdbc | |||
| 7b1e02e8cf | |||
| 257f09da4f | |||
| ecd58fa501 | |||
| c4ece4932e | |||
| d41ebf190b | |||
| 1823993e7c | |||
| d349d2a0cd | |||
| a79491dd16 |
@@ -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:
|
||||||
|
|||||||
+17
-14
@@ -2844,7 +2844,7 @@ registerSubControls('lfo', [
|
|||||||
['dcoffset', 'dc'],
|
['dcoffset', 'dc'],
|
||||||
['shape', 'sh'],
|
['shape', 'sh'],
|
||||||
['skew', 'sk'],
|
['skew', 'sk'],
|
||||||
['curve'],
|
['curve', 'cu'],
|
||||||
['sync', 's'],
|
['sync', 's'],
|
||||||
['fxi'],
|
['fxi'],
|
||||||
]);
|
]);
|
||||||
@@ -2872,7 +2872,7 @@ registerSubControls('bmod', [
|
|||||||
['fxi'],
|
['fxi'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Pattern.prototype.modulate = function (type, config, id) {
|
Pattern.prototype.modulate = function (type, config, idPat) {
|
||||||
config = { control: undefined, ...config };
|
config = { control: undefined, ...config };
|
||||||
const modulatorKeys = ['lfo', 'env', 'bmod'];
|
const modulatorKeys = ['lfo', 'env', 'bmod'];
|
||||||
if (!modulatorKeys.includes(type)) {
|
if (!modulatorKeys.includes(type)) {
|
||||||
@@ -2881,11 +2881,14 @@ Pattern.prototype.modulate = function (type, config, id) {
|
|||||||
}
|
}
|
||||||
let output = this;
|
let output = this;
|
||||||
let defaultValue = undefined;
|
let defaultValue = undefined;
|
||||||
|
// Copy value into a temporary `v` container and attach a single `id` (to be shared across
|
||||||
|
// each config entry). At the output we destructure and throw away the id
|
||||||
|
output = output.fmap((v) => (id) => ({ v, id })).appLeft(reify(idPat));
|
||||||
for (const [rawKey, value] of Object.entries(config)) {
|
for (const [rawKey, value] of Object.entries(config)) {
|
||||||
const key = getMainSubcontrolName(type, rawKey);
|
const key = getMainSubcontrolName(type, rawKey);
|
||||||
const valuePat = reify(value);
|
const valuePat = reify(value);
|
||||||
output = output
|
output = output
|
||||||
.fmap((v) => (c) => {
|
.fmap(({ v, id }) => (c) => {
|
||||||
if (defaultValue === undefined) {
|
if (defaultValue === undefined) {
|
||||||
// default control to the control set just before this in the chain
|
// default control to the control set just before this in the chain
|
||||||
// e.g. pat.gain(0.5).lfo({..}) will be a gain-LFO
|
// e.g. pat.gain(0.5).lfo({..}) will be a gain-LFO
|
||||||
@@ -2900,17 +2903,17 @@ Pattern.prototype.modulate = function (type, config, id) {
|
|||||||
id ??= t.__ids.size;
|
id ??= t.__ids.size;
|
||||||
t[id] ??= { control: defaultValue };
|
t[id] ??= { control: defaultValue };
|
||||||
t.__ids.add(id); // keeps track of insertion order
|
t.__ids.add(id); // keeps track of insertion order
|
||||||
if (c === undefined) return v;
|
if (c === undefined) return { v, id };
|
||||||
if (key === 'control' || key === 'subControl') {
|
if (key === 'control' || key === 'subControl') {
|
||||||
t[id][key] = getControlName(c);
|
t[id][key] = getControlName(c);
|
||||||
} else {
|
} else {
|
||||||
t[id][key] = c;
|
t[id][key] = c;
|
||||||
}
|
}
|
||||||
return v;
|
return { v, id };
|
||||||
})
|
})
|
||||||
.appLeft(valuePat);
|
.appLeft(valuePat);
|
||||||
}
|
}
|
||||||
return output;
|
return output.fmap(({ v }) => v);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2925,15 +2928,15 @@ Pattern.prototype.modulate = function (type, config, id) {
|
|||||||
*
|
*
|
||||||
* @name lfo
|
* @name lfo
|
||||||
* @param {Object} config LFO configuration.
|
* @param {Object} config LFO configuration.
|
||||||
* @param {string | Pattern} [config.control] Node to modulate. Aliases: t
|
* @param {string | Pattern} [config.control] Node to modulate. Aliases: c
|
||||||
* @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc, p
|
* @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc
|
||||||
* @param {number | Pattern} [config.rate] Modulation rate. Aliases: rate, r
|
* @param {number | Pattern} [config.rate] Modulation rate. Aliases: r
|
||||||
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
||||||
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
||||||
* @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc
|
* @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc
|
||||||
* @param {number | Pattern} [config.shape] Shape index. Aliases: sh
|
* @param {number | Pattern} [config.shape] Shape index. Aliases: sh
|
||||||
* @param {number | Pattern} [config.skew] Skew amount. Aliases: sk
|
* @param {number | Pattern} [config.skew] Skew amount. Aliases: sk
|
||||||
* @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: c
|
* @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: cu
|
||||||
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
|
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
|
||||||
* @param {number | Pattern} [config.fxi] FX index to target
|
* @param {number | Pattern} [config.fxi] FX index to target
|
||||||
* @param {string | Pattern} id ID to use for this modulator
|
* @param {string | Pattern} id ID to use for this modulator
|
||||||
@@ -2978,8 +2981,8 @@ export const lfo = (config) => pure({}).lfo(config);
|
|||||||
*
|
*
|
||||||
* @name env
|
* @name env
|
||||||
* @param {Object} config Envelope configuration.
|
* @param {Object} config Envelope configuration.
|
||||||
* @param {string | Pattern} [config.control] Node to modulate. Aliases: t
|
* @param {string | Pattern} [config.control] Node to modulate. Aliases: c
|
||||||
* @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc, p
|
* @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc
|
||||||
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
||||||
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
||||||
* @param {number | Pattern} [config.attack] Time to reach depth. Aliases: att, a
|
* @param {number | Pattern} [config.attack] Time to reach depth. Aliases: att, a
|
||||||
@@ -3039,8 +3042,8 @@ export const env = (config) => pure({}).env(config);
|
|||||||
* @name bmod
|
* @name bmod
|
||||||
* @param {Object} config Bus modulation configuration.
|
* @param {Object} config Bus modulation configuration.
|
||||||
* @param {string | Pattern} [config.bus] Bus to get modulation signal from
|
* @param {string | Pattern} [config.bus] Bus to get modulation signal from
|
||||||
* @param {string | Pattern} [config.control] Node to modulate. Aliases: t
|
* @param {string | Pattern} [config.control] Node to modulate. Aliases: c
|
||||||
* @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc, p
|
* @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc
|
||||||
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
||||||
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
||||||
* @param {number | Pattern} [config.dc] DC offset prior to application
|
* @param {number | Pattern} [config.dc] DC offset prior to application
|
||||||
|
|||||||
@@ -46,4 +46,4 @@ all(osc)
|
|||||||
|
|
||||||
[open in repl](https://strudel.cc/#JDogcygiYmQqNCIpCgphbGwob3NjKQ%3D%3D)
|
[open in repl](https://strudel.cc/#JDogcygiYmQqNCIpCgphbGwob3NjKQ%3D%3D)
|
||||||
|
|
||||||
You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.cc/learn/input-output/#superdirt-api)
|
You can read more about [how to use Superdirt with Strudel](https://strudel.cc/learn/input-output/#oscsuperdirtstrudeldirt) in the tutorial.
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -607,7 +607,11 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
|
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
|
||||||
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
|
|
||||||
stretch !== undefined && chain.connect(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
if (stretch !== undefined) {
|
||||||
|
const phaseVocoder = getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch });
|
||||||
|
chain.connect(phaseVocoder);
|
||||||
|
fxNodes['stretch'] = [phaseVocoder];
|
||||||
|
}
|
||||||
|
|
||||||
if (fx.transient !== undefined) {
|
if (fx.transient !== undefined) {
|
||||||
const transProcessor = getWorklet(
|
const transProcessor = getWorklet(
|
||||||
@@ -821,6 +825,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
// panning
|
// panning
|
||||||
if (fx.pan !== undefined) {
|
if (fx.pan !== undefined) {
|
||||||
const panner = ac.createStereoPanner();
|
const panner = ac.createStereoPanner();
|
||||||
|
fxNodes['pan'] = [panner];
|
||||||
panner.pan.value = 2 * fx.pan - 1;
|
panner.pan.value = 2 * fx.pan - 1;
|
||||||
chain.connect(panner);
|
chain.connect(panner);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ note("c3 [e3 g3]*2")
|
|||||||
is transpiled to:
|
is transpiled to:
|
||||||
|
|
||||||
```strudel
|
```strudel
|
||||||
note(m('c3 [e3 g3]', 5))
|
note(m('c3 [e3 g3]*2', 5))
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, the string is wrapped in `m`, which will create a pattern from a mini-notation string. As the second parameter, it gets passed source code location of the string, which enables highlighting active events later.
|
Here, the string is wrapped in `m`, which will create a pattern from a mini-notation string. As the second parameter, it gets passed source code location of the string, which enables highlighting active events later.
|
||||||
|
|||||||
@@ -377,4 +377,4 @@ insect [crow metal] - -,
|
|||||||
punchcard
|
punchcard
|
||||||
/>
|
/>
|
||||||
|
|
||||||
Now that we know the basics of how to make beats, let's look at how we can play [notes](/workshop/first-notes)
|
Now that we know the basics of how to make beats, let's look at how we can play [notes](/workshop/first-notes).
|
||||||
|
|||||||
Reference in New Issue
Block a user