Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1829d8cadf | |||
| 02efc090ef | |||
| b184348f55 | |||
| 06743cb072 | |||
| 6fa66fb1d0 | |||
| 23c0ab9648 | |||
| 6422047cff | |||
| 46c2c72637 | |||
| c088590244 | |||
| 192ba69682 | |||
| 2cc428e968 | |||
| 15c451a6d9 | |||
| b46e8c07be | |||
| 961e931c31 | |||
| b1d49552ae | |||
| d31d154841 | |||
| d03892bbcf | |||
| 0cf0c1c585 | |||
| 0695b4bee9 | |||
| 0cfec3a7bd | |||
| 6224cd40d4 | |||
| 18ae82174d | |||
| aa1b9d11dc | |||
| 0a82471112 | |||
| 8052e10f0c | |||
| 1146f6ba52 | |||
| 738cea0025 | |||
| 6c28398d20 | |||
| 4fe75143d0 | |||
| 3cd59cdea5 | |||
| 9dca7eb878 | |||
| b45fd5ce3a | |||
| 229b1470f7 | |||
| 0a349e1707 | |||
| d176e9ccf0 | |||
| 710c3acb5d | |||
| a7f5d0e0c1 | |||
| 9928526dda | |||
| 2f8111be0e | |||
| 68113937ff | |||
| c598fa026b | |||
| 491c99c348 | |||
| d49aa8242a | |||
| 9ffbc06cf9 | |||
| af52ac30ba | |||
| 7aea1b5fc8 | |||
| 500ddf3946 | |||
| 3431eaf309 | |||
| 9a13c58583 | |||
| 77a48e8351 | |||
| 6cb156d876 | |||
| d8677c6261 | |||
| 6ef9ca18e9 | |||
| 6667738dc9 | |||
| 4f7415b0ec | |||
| c6f3a8b7c7 | |||
| 4229d673c9 | |||
| 98b7859605 | |||
| a8db707440 | |||
| 873a412179 | |||
| 0778b4809e | |||
| f85fb35c66 | |||
| 5ada0daf40 | |||
| 2ed5f5aa65 | |||
| ae5e6e3ab8 | |||
| 9ba7b9b4d4 | |||
| a45f1fba45 | |||
| e6b47d2cf2 | |||
| 7adfe6f2df | |||
| 45df7bfea3 | |||
| 106772e480 | |||
| e7e60d3ed8 | |||
| 78f6fdf169 | |||
| e01c6dc18d | |||
| 51dd408733 | |||
| 1ff7548b52 | |||
| f7fe44063a | |||
| 0355c358b8 | |||
| 4e0a60db00 | |||
| 3604d3940c | |||
| 02b9bd82cf | |||
| 1bc86c359e | |||
| 1e2c26123b |
@@ -16,34 +16,40 @@
|
||||
</div>
|
||||
<div id="output"></div>
|
||||
<script type="module">
|
||||
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.6.8';
|
||||
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0';
|
||||
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.6.0';
|
||||
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel/core@0.11.0';
|
||||
import { mini } from 'https://cdn.skypack.dev/@strudel/mini@0.11.0';
|
||||
import { transpiler } from 'https://cdn.skypack.dev/@strudel/transpiler@0.11.0';
|
||||
import {
|
||||
getAudioContext,
|
||||
webaudioOutput,
|
||||
initAudioOnFirstClick,
|
||||
} from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0';
|
||||
registerSynthSounds,
|
||||
} from 'https://cdn.skypack.dev/@strudel/webaudio@0.11.0';
|
||||
|
||||
initAudioOnFirstClick();
|
||||
const ctx = getAudioContext();
|
||||
const input = document.getElementById('text');
|
||||
input.innerHTML = getTune();
|
||||
|
||||
evalScope(
|
||||
const loadModules = evalScope(
|
||||
controls,
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.6.0'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0'),
|
||||
import('https://cdn.skypack.dev/@strudel/core@0.11.0'),
|
||||
import('https://cdn.skypack.dev/@strudel/mini@0.11.0'),
|
||||
import('https://cdn.skypack.dev/@strudel/tonal@0.11.0'),
|
||||
import('https://cdn.skypack.dev/@strudel/webaudio@0.11.0'),
|
||||
);
|
||||
|
||||
const initAudio = Promise.all([initAudioOnFirstClick(), registerSynthSounds()]);
|
||||
|
||||
const { evaluate } = repl({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => ctx.currentTime,
|
||||
transpiler,
|
||||
});
|
||||
document.getElementById('start').addEventListener('click', () => evaluate(input.value));
|
||||
document.getElementById('start').addEventListener('click', async () => {
|
||||
await loadModules;
|
||||
await initAudio;
|
||||
evaluate(input.value);
|
||||
});
|
||||
|
||||
function getTune() {
|
||||
return `await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
|
||||
<script src="https://unpkg.com/@strudel/embed@0.11.0"></script>
|
||||
<!-- <script src="./embed.js"></script> -->
|
||||
<strudel-repl>
|
||||
<!--
|
||||
|
||||
@@ -57,6 +57,9 @@ const visibleMiniLocations = StateField.define({
|
||||
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range
|
||||
const haps = new Map();
|
||||
for (let hap of e.value.haps) {
|
||||
if (!hap.context?.locations || !hap.whole) {
|
||||
continue;
|
||||
}
|
||||
for (let { start, end } of hap.context.locations) {
|
||||
let id = `${start}:${end}`;
|
||||
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) {
|
||||
@@ -64,7 +67,6 @@ const visibleMiniLocations = StateField.define({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visible = { atTime: e.value.atTime, haps };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -40,7 +40,7 @@ const generic_params = [
|
||||
* @name n
|
||||
* @param {number | Pattern} value sample index starting from 0
|
||||
* @example
|
||||
* s("bd sd,hh*3").n("<0 1>")
|
||||
* s("bd sd [~ bd] sd,hh*6").n("<0 1>")
|
||||
*/
|
||||
// also see https://github.com/tidalcycles/strudel/pull/63
|
||||
['n'],
|
||||
@@ -82,7 +82,7 @@ const generic_params = [
|
||||
* @name gain
|
||||
* @param {number | Pattern} amount gain.
|
||||
* @example
|
||||
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1")
|
||||
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2)
|
||||
*
|
||||
*/
|
||||
['gain'],
|
||||
@@ -91,13 +91,13 @@ const generic_params = [
|
||||
*
|
||||
* @name postgain
|
||||
* @example
|
||||
* s("bd sd,hh*4")
|
||||
* s("bd sd [~ bd] sd,hh*8")
|
||||
* .compressor("-20:20:10:.002:.02").postgain(1.5)
|
||||
*
|
||||
*/
|
||||
['postgain'],
|
||||
/**
|
||||
* Like {@link gain}, but linear.
|
||||
* Like `gain`, but linear.
|
||||
*
|
||||
* @name amp
|
||||
* @param {number | Pattern} amount gain.
|
||||
@@ -114,7 +114,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} attack time in seconds.
|
||||
* @synonyms att
|
||||
* @example
|
||||
* note("c3 e3").attack("<0 .1 .5>")
|
||||
* note("c3 e3 f3 g3").attack("<0 .1 .5>")
|
||||
*
|
||||
*/
|
||||
['attack', 'att'],
|
||||
@@ -128,7 +128,7 @@ const generic_params = [
|
||||
* @name fmh
|
||||
* @param {number | Pattern} harmonicity
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* note("c e g b g e")
|
||||
* .fm(4)
|
||||
* .fmh("<1 2 1.5 1.61>")
|
||||
* .scope()
|
||||
@@ -143,7 +143,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} brightness modulation index
|
||||
* @synonyms fmi
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* note("c e g b g e")
|
||||
* .fm("<0 1 2 8 32>")
|
||||
* .scope()
|
||||
*
|
||||
@@ -156,7 +156,7 @@ const generic_params = [
|
||||
* @name fmenv
|
||||
* @param {number | Pattern} type lin | exp
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* note("c e g b g e")
|
||||
* .fm(4)
|
||||
* .fmdecay(.2)
|
||||
* .fmsustain(0)
|
||||
@@ -171,7 +171,7 @@ const generic_params = [
|
||||
* @name fmattack
|
||||
* @param {number | Pattern} time attack time
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* note("c e g b g e")
|
||||
* .fm(4)
|
||||
* .fmattack("<0 .05 .1 .2>")
|
||||
* .scope()
|
||||
@@ -184,7 +184,7 @@ const generic_params = [
|
||||
* @name fmdecay
|
||||
* @param {number | Pattern} time decay time
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* note("c e g b g e")
|
||||
* .fm(4)
|
||||
* .fmdecay("<.01 .05 .1 .2>")
|
||||
* .fmsustain(.4)
|
||||
@@ -198,7 +198,7 @@ const generic_params = [
|
||||
* @name fmsustain
|
||||
* @param {number | Pattern} level sustain level
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* note("c e g b g e")
|
||||
* .fm(4)
|
||||
* .fmdecay(.1)
|
||||
* .fmsustain("<1 .75 .5 0>")
|
||||
@@ -216,7 +216,7 @@ const generic_params = [
|
||||
* @name bank
|
||||
* @param {string | Pattern} bank the name of the bank
|
||||
* @example
|
||||
* s("bd sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
|
||||
* s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
|
||||
*
|
||||
*/
|
||||
['bank'],
|
||||
@@ -231,7 +231,7 @@ const generic_params = [
|
||||
* @name decay
|
||||
* @param {number | Pattern} time decay time in seconds
|
||||
* @example
|
||||
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||
* note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||
*
|
||||
*/
|
||||
['decay', 'dec'],
|
||||
@@ -242,7 +242,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} gain sustain level between 0 and 1
|
||||
* @synonyms sus
|
||||
* @example
|
||||
* note("c3 e3").decay(.2).sustain("<0 .1 .4 .6 1>")
|
||||
* note("c3 e3 f3 g3").decay(.2).sustain("<0 .1 .4 .6 1>")
|
||||
*
|
||||
*/
|
||||
['sustain', 'sus'],
|
||||
@@ -267,7 +267,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} frequency center frequency
|
||||
* @synonyms bandf, bp
|
||||
* @example
|
||||
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
|
||||
* s("bd sd [~ bd] sd,hh*6").bpf("<1000 2000 4000 8000>")
|
||||
*
|
||||
*/
|
||||
[['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'],
|
||||
@@ -279,7 +279,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} q q factor
|
||||
* @synonyms bandq
|
||||
* @example
|
||||
* s("bd sd").bpf(500).bpq("<0 1 2 3>")
|
||||
* s("bd sd [~ bd] sd").bpf(500).bpq("<0 1 2 3>")
|
||||
*
|
||||
*/
|
||||
// currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496
|
||||
@@ -293,7 +293,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
|
||||
* @example
|
||||
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/Dirt-Samples/master/')
|
||||
* s("rave").begin("<0 .25 .5 .75>")
|
||||
* s("rave").begin("<0 .25 .5 .75>").fast(2)
|
||||
*
|
||||
*/
|
||||
['begin'],
|
||||
@@ -304,7 +304,7 @@ const generic_params = [
|
||||
* @name end
|
||||
* @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
|
||||
* @example
|
||||
* s("bd*2,oh*4").end("<.1 .2 .5 1>")
|
||||
* s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2)
|
||||
*
|
||||
*/
|
||||
['end'],
|
||||
@@ -376,7 +376,7 @@ const generic_params = [
|
||||
* @name coarse
|
||||
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
|
||||
* @example
|
||||
* s("bd sd,hh*4").coarse("<1 4 8 16 32>")
|
||||
* s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>")
|
||||
*
|
||||
*/
|
||||
['coarse'],
|
||||
@@ -463,7 +463,7 @@ const generic_params = [
|
||||
* @name cut
|
||||
* @param {number | Pattern} group cut group number
|
||||
* @example
|
||||
* s("rd*4").cut(1)
|
||||
* s("[oh hh]*4").cut(1)
|
||||
*
|
||||
*/
|
||||
['cut'],
|
||||
@@ -476,9 +476,9 @@ const generic_params = [
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms cutoff, ctf, lp
|
||||
* @example
|
||||
* s("bd sd,hh*3").lpf("<4000 2000 1000 500 200 100>")
|
||||
* s("bd sd [~ bd] sd,hh*6").lpf("<4000 2000 1000 500 200 100>")
|
||||
* @example
|
||||
* s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30")
|
||||
* s("bd*16").lpf("1000:0 1000:10 1000:20 1000:30")
|
||||
*
|
||||
*/
|
||||
[['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'],
|
||||
@@ -489,7 +489,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_
|
||||
* @synonyms lpe
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpa(.5)
|
||||
@@ -502,7 +502,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_
|
||||
* @synonyms hpe
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpa(.5)
|
||||
@@ -515,7 +515,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_
|
||||
* @synonyms bpe
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpa(.5)
|
||||
@@ -528,7 +528,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} attack time of the filter envelope
|
||||
* @synonyms lpa
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpa("<.5 .25 .1 .01>/4")
|
||||
@@ -541,7 +541,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} attack time of the highpass filter envelope
|
||||
* @synonyms hpa
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpa("<.5 .25 .1 .01>/4")
|
||||
@@ -554,7 +554,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} attack time of the bandpass filter envelope
|
||||
* @synonyms bpa
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpa("<.5 .25 .1 .01>/4")
|
||||
@@ -567,7 +567,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} decay time of the filter envelope
|
||||
* @synonyms lpd
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpd("<.5 .25 .1 0>/4")
|
||||
@@ -581,7 +581,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} decay time of the highpass filter envelope
|
||||
* @synonyms hpd
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpd("<.5 .25 .1 0>/4")
|
||||
@@ -595,7 +595,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} decay time of the bandpass filter envelope
|
||||
* @synonyms bpd
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpd("<.5 .25 .1 0>/4")
|
||||
@@ -609,7 +609,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} sustain amplitude of the lowpass filter envelope
|
||||
* @synonyms lps
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpd(.5)
|
||||
@@ -623,7 +623,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} sustain amplitude of the highpass filter envelope
|
||||
* @synonyms hps
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpd(.5)
|
||||
@@ -637,7 +637,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} sustain amplitude of the bandpass filter envelope
|
||||
* @synonyms bps
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpd(.5)
|
||||
@@ -651,7 +651,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} release time of the filter envelope
|
||||
* @synonyms lpr
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .clip(.5)
|
||||
* .lpf(500)
|
||||
@@ -666,7 +666,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} release time of the highpass filter envelope
|
||||
* @synonyms hpr
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .clip(.5)
|
||||
* .hpf(500)
|
||||
@@ -681,7 +681,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} release time of the bandpass filter envelope
|
||||
* @synonyms bpr
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .clip(.5)
|
||||
* .bpf(500)
|
||||
@@ -695,11 +695,11 @@ const generic_params = [
|
||||
* @name ftype
|
||||
* @param {number | Pattern} type 12db (default) or 24db
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* note("c2 e2 f2 g2")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .bpenv(4)
|
||||
* .ftype("<12db 24db>")
|
||||
* .ftype("12db 24db")
|
||||
*/
|
||||
['ftype'],
|
||||
['fanchor'],
|
||||
@@ -712,9 +712,9 @@ const generic_params = [
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms hp, hcutoff
|
||||
* @example
|
||||
* s("bd sd,hh*4").hpf("<4000 2000 1000 500 200 100>")
|
||||
* s("bd sd [~ bd] sd,hh*8").hpf("<4000 2000 1000 500 200 100>")
|
||||
* @example
|
||||
* s("bd sd,hh*4").hpf("<2000 2000:25>")
|
||||
* s("bd sd [~ bd] sd,hh*8").hpf("<2000 2000:25>")
|
||||
*
|
||||
*/
|
||||
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496
|
||||
@@ -726,11 +726,11 @@ const generic_params = [
|
||||
* @synonyms vibrato, v
|
||||
* @param {number | Pattern} frequency of the vibrato in hertz
|
||||
* @example
|
||||
* note("a")
|
||||
* note("a e")
|
||||
* .vib("<.5 1 2 4 8 16>")
|
||||
* @example
|
||||
* // change the modulation depth with ":"
|
||||
* note("a")
|
||||
* note("a e")
|
||||
* .vib("<.5 1 2 4 8 16>:12")
|
||||
*/
|
||||
[['vib', 'vibmod'], 'vibrato', 'v'],
|
||||
@@ -750,11 +750,11 @@ const generic_params = [
|
||||
* @synonyms vmod
|
||||
* @param {number | Pattern} depth of vibrato (in semitones)
|
||||
* @example
|
||||
* note("a").vib(4)
|
||||
* note("a e").vib(4)
|
||||
* .vibmod("<.25 .5 1 2 12>")
|
||||
* @example
|
||||
* // change the vibrato frequency with ":"
|
||||
* note("a")
|
||||
* note("a e")
|
||||
* .vibmod("<.25 .5 1 2 12>:8")
|
||||
*/
|
||||
[['vibmod', 'vib'], 'vmod'],
|
||||
@@ -766,7 +766,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @synonyms hresonance
|
||||
* @example
|
||||
* s("bd sd,hh*4").hpf(2000).hpq("<0 10 20 30>")
|
||||
* s("bd sd [~ bd] sd,hh*8").hpf(2000).hpq("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
['hresonance', 'hpq'],
|
||||
@@ -777,7 +777,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @synonyms resonance
|
||||
* @example
|
||||
* s("bd sd,hh*4").lpf(2000).lpq("<0 10 20 30>")
|
||||
* s("bd sd [~ bd] sd,hh*8").lpf(2000).lpq("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
// currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496
|
||||
@@ -804,7 +804,7 @@ const generic_params = [
|
||||
* @name delay
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd").delay("<0 .25 .5 1>")
|
||||
* s("bd bd").delay("<0 .25 .5 1>")
|
||||
* @example
|
||||
* s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7")
|
||||
*
|
||||
@@ -818,7 +818,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} feedback between 0 and 1
|
||||
* @synonyms delayfb, dfb
|
||||
* @example
|
||||
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>").slow(2)
|
||||
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>")
|
||||
*
|
||||
*/
|
||||
['delayfeedback', 'delayfb', 'dfb'],
|
||||
@@ -829,7 +829,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} seconds between 0 and Infinity
|
||||
* @synonyms delayt, dt
|
||||
* @example
|
||||
* s("bd").delay(.25).delaytime("<.125 .25 .5 1>").slow(2)
|
||||
* s("bd bd").delay(.25).delaytime("<.125 .25 .5 1>")
|
||||
*
|
||||
*/
|
||||
['delaytime', 'delayt', 'dt'],
|
||||
@@ -856,7 +856,7 @@ const generic_params = [
|
||||
*/
|
||||
['detune', 'det'],
|
||||
/**
|
||||
* Set dryness of reverb. See {@link room} and {@link size} for more information about reverb.
|
||||
* Set dryness of reverb. See `room` and `size` for more information about reverb.
|
||||
*
|
||||
* @name dry
|
||||
* @param {number | Pattern} dry 0 = wet, 1 = dry
|
||||
@@ -868,7 +868,7 @@ const generic_params = [
|
||||
['dry'],
|
||||
// TODO: does not seem to do anything
|
||||
/*
|
||||
* Used when using {@link begin}/{@link end} or {@link chop}/{@link striate} and friends, to change the fade out time of the 'grain' envelope.
|
||||
* Used when using `begin`/`end` or `chop`/`striate` and friends, to change the fade out time of the 'grain' envelope.
|
||||
*
|
||||
* @name fadeTime
|
||||
* @param {number | Pattern} time between 0 and 1
|
||||
@@ -899,7 +899,7 @@ const generic_params = [
|
||||
* @synonyms patt
|
||||
* @param {number | Pattern} time time in seconds
|
||||
* @example
|
||||
* note("<c eb g bb>").pattack("<0 .1 .25 .5>")
|
||||
* note("c eb g bb").pattack("0 .1 .25 .5").slow(2)
|
||||
*
|
||||
*/
|
||||
['pattack', 'patt'],
|
||||
@@ -947,7 +947,7 @@ const generic_params = [
|
||||
* @name pcurve
|
||||
* @param {number | Pattern} type 0 = linear, 1 = exponential
|
||||
* @example
|
||||
* note("g1*2")
|
||||
* note("g1*4")
|
||||
* .s("sine").pdec(.5)
|
||||
* .penv(32)
|
||||
* .pcurve("<0 1>")
|
||||
@@ -963,7 +963,7 @@ const generic_params = [
|
||||
* @name panchor
|
||||
* @param {number | Pattern} anchor anchor offset
|
||||
* @example
|
||||
* note("c").penv(12).panchor("<0 .5 1 .5>")
|
||||
* note("c c4").penv(12).panchor("<0 .5 1 .5>")
|
||||
*
|
||||
*/
|
||||
['panchor'],
|
||||
@@ -1062,8 +1062,8 @@ const generic_params = [
|
||||
* @param {number | Pattern} number
|
||||
* @example
|
||||
* stack(
|
||||
* s("hh*3").delay(.5).delaytime(.25).orbit(1),
|
||||
* s("~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||
* s("hh*6").delay(.5).delaytime(.25).orbit(1),
|
||||
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||
* )
|
||||
*/
|
||||
['orbit'],
|
||||
@@ -1076,6 +1076,8 @@ const generic_params = [
|
||||
* @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
|
||||
* @example
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>")
|
||||
* @example
|
||||
* s("bd rim sd rim bd ~ cp rim").pan(sine.slow(2))
|
||||
*
|
||||
*/
|
||||
['pan'],
|
||||
@@ -1133,9 +1135,9 @@ const generic_params = [
|
||||
* @name room
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd sd").room("<0 .2 .4 .6 .8 1>")
|
||||
* s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>")
|
||||
* @example
|
||||
* s("bd sd").room("<0.9:1 0.9:4>")
|
||||
* s("bd sd [~ bd] sd").room("<0.9:1 0.9:4>")
|
||||
*
|
||||
*/
|
||||
[['room', 'size']],
|
||||
@@ -1147,9 +1149,9 @@ const generic_params = [
|
||||
* @synonyms rlp
|
||||
* @param {number} frequency between 0 and 20000hz
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(10000)
|
||||
* s("bd sd [~ bd] sd").room(0.5).rlp(10000)
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(5000)
|
||||
* s("bd sd [~ bd] sd").room(0.5).rlp(5000)
|
||||
*/
|
||||
['roomlp', 'rlp'],
|
||||
/**
|
||||
@@ -1160,9 +1162,9 @@ const generic_params = [
|
||||
* @synonyms rdim
|
||||
* @param {number} frequency between 0 and 20000hz
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(10000).rdim(8000)
|
||||
* s("bd sd [~ bd] sd").room(0.5).rlp(10000).rdim(8000)
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(5000).rdim(400)
|
||||
* s("bd sd [~ bd] sd").room(0.5).rlp(5000).rdim(400)
|
||||
*
|
||||
*/
|
||||
['roomdim', 'rdim'],
|
||||
@@ -1174,9 +1176,9 @@ const generic_params = [
|
||||
* @synonyms rfade
|
||||
* @param {number} seconds for the reverb to fade
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(10000).rfade(0.5)
|
||||
* s("bd sd [~ bd] sd").room(0.5).rlp(10000).rfade(0.5)
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(5000).rfade(4)
|
||||
* s("bd sd [~ bd] sd").room(0.5).rlp(5000).rfade(4)
|
||||
*
|
||||
*/
|
||||
['roomfade', 'rfade'],
|
||||
@@ -1186,25 +1188,25 @@ const generic_params = [
|
||||
* @param {string | Pattern} sample to use as an impulse response
|
||||
* @synonyms ir
|
||||
* @example
|
||||
* s("bd sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
|
||||
* s("bd sd [~ bd] sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
|
||||
*
|
||||
*/
|
||||
[['ir', 'i'], 'iresponse'],
|
||||
/**
|
||||
* Sets the room size of the reverb, see {@link room}.
|
||||
* Sets the room size of the reverb, see `room`.
|
||||
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
|
||||
*
|
||||
* @name roomsize
|
||||
* @param {number | Pattern} size between 0 and 10
|
||||
* @synonyms rsize, sz, size
|
||||
* @example
|
||||
* s("bd sd").room(.8).rsize(1)
|
||||
* s("bd sd [~ bd] sd").room(.8).rsize(1)
|
||||
* @example
|
||||
* s("bd sd").room(.8).rsize(4)
|
||||
* s("bd sd [~ bd] sd").room(.8).rsize(4)
|
||||
*
|
||||
*/
|
||||
// TODO: find out why :
|
||||
// s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc()
|
||||
// s("bd sd [~ bd] sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc()
|
||||
// .. does not work. Is it because room is only one effect?
|
||||
['roomsize', 'size', 'sz', 'rsize'],
|
||||
// ['sagogo'],
|
||||
@@ -1217,7 +1219,7 @@ const generic_params = [
|
||||
* @name shape
|
||||
* @param {number | Pattern} distortion between 0 and 1
|
||||
* @example
|
||||
* s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>")
|
||||
* s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>")
|
||||
*
|
||||
*/
|
||||
['shape'],
|
||||
@@ -1227,7 +1229,7 @@ const generic_params = [
|
||||
*
|
||||
* @name compressor
|
||||
* @example
|
||||
* s("bd sd,hh*4")
|
||||
* s("bd sd [~ bd] sd,hh*8")
|
||||
* .compressor("-20:20:10:.002:.02")
|
||||
*
|
||||
*/
|
||||
@@ -1242,14 +1244,14 @@ const generic_params = [
|
||||
* @name speed
|
||||
* @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards.
|
||||
* @example
|
||||
* s("bd").speed("<1 2 4 1 -2 -4>")
|
||||
* s("bd*6").speed("1 2 4 1 -2 -4")
|
||||
* @example
|
||||
* speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
|
||||
*
|
||||
*/
|
||||
['speed'],
|
||||
/**
|
||||
* Used in conjunction with {@link speed}, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
|
||||
* Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
|
||||
*
|
||||
* @name unit
|
||||
* @param {number | string | Pattern} unit see description above
|
||||
@@ -1287,8 +1289,10 @@ const generic_params = [
|
||||
* @name vowel
|
||||
* @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ.
|
||||
* @example
|
||||
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
|
||||
* note("[c2 <eb2 <g2 g1>>]*2").s('sawtooth')
|
||||
* .vowel("<a e i <o u>>")
|
||||
* @example
|
||||
* s("bd sd mt ht bd [~ cp] ht lt").vowel("[a|e|i|o|u]")
|
||||
*
|
||||
*/
|
||||
['vowel'],
|
||||
@@ -1463,7 +1467,7 @@ controls.createParams = (...names) =>
|
||||
* @param {number | Pattern} gain sustain level (0 to 1)
|
||||
* @param {number | Pattern} time release time in seconds
|
||||
* @example
|
||||
* note("<c3 bb2 f3 eb3>").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2")
|
||||
* note("[c3 bb2 f3 eb3]*2").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2")
|
||||
*/
|
||||
controls.adsr = register('adsr', (adsr, pat) => {
|
||||
adsr = !Array.isArray(adsr) ? [adsr] : adsr;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { logger } from './logger.mjs';
|
||||
export class Cyclist {
|
||||
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
||||
this.started = false;
|
||||
this.cps = 1;
|
||||
this.cps = 0.5;
|
||||
this.num_ticks_since_cps_change = 0;
|
||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||
this.lastBegin = 0; // query begin of last tick
|
||||
@@ -41,7 +41,7 @@ export class Cyclist {
|
||||
this.lastEnd = end;
|
||||
|
||||
// query the pattern for events
|
||||
const haps = this.pattern.queryArc(begin, end);
|
||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
||||
|
||||
const tickdeadline = phase - time; // time left until the phase is a whole number
|
||||
this.lastTick = time + tickdeadline;
|
||||
@@ -96,7 +96,7 @@ export class Cyclist {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
setCps(cps = 1) {
|
||||
setCps(cps = 0.5) {
|
||||
if (this.cps === cps) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ export class Drawer {
|
||||
this.lastFrame = phase;
|
||||
this.visibleHaps = (this.visibleHaps || [])
|
||||
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
|
||||
.filter((h) => h.whole.end >= phase - lookbehind - lookahead)
|
||||
.filter((h) => h.whole?.end >= phase - lookbehind - lookahead)
|
||||
// add new haps with onset (think right edge bars scrolling in)
|
||||
.concat(haps.filter((h) => h.hasOnset()));
|
||||
const time = phase - lookahead;
|
||||
|
||||
@@ -148,7 +148,7 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @example
|
||||
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
|
||||
* note("c3").euclidLegato(3,8)
|
||||
*/
|
||||
|
||||
const _euclidLegato = function (pulses, steps, rotation, pat) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/core",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -26,7 +26,7 @@ export class Pattern {
|
||||
/**
|
||||
* Create a pattern. As an end user, you will most likely not create a Pattern directly.
|
||||
*
|
||||
* @param {function} query - The function that maps a {@link State} to an array of {@link Hap}.
|
||||
* @param {function} query - The function that maps a `State` to an array of `Hap`.
|
||||
* @noAutocomplete
|
||||
*/
|
||||
constructor(query) {
|
||||
@@ -39,7 +39,7 @@ export class Pattern {
|
||||
|
||||
/**
|
||||
* Returns a new pattern, with the function applied to the value of
|
||||
* each hap. It has the alias {@link Pattern#fmap}.
|
||||
* each hap. It has the alias `fmap`.
|
||||
* @synonyms fmap
|
||||
* @param {Function} func to to apply to the value
|
||||
* @returns Pattern
|
||||
@@ -51,7 +51,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* see {@link Pattern#withValue}
|
||||
* see `withValue`
|
||||
* @noAutocomplete
|
||||
*/
|
||||
fmap(func) {
|
||||
@@ -115,7 +115,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* As with {@link Pattern#appBoth}, but the `whole` timespan is not the intersection,
|
||||
* As with `appBoth`, but the `whole` timespan is not the intersection,
|
||||
* but the timespan from the function of patterns that this method is called
|
||||
* on. In practice, this means that the pattern structure, including onsets,
|
||||
* are preserved from the pattern of functions (often referred to as the left
|
||||
@@ -148,7 +148,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* As with {@link Pattern#appLeft}, but `whole` timespans are instead taken from the
|
||||
* As with `appLeft`, but `whole` timespans are instead taken from the
|
||||
* pattern of values, i.e. structure is preserved from the right hand/outer
|
||||
* pattern.
|
||||
* @param {Pattern} pat_val
|
||||
@@ -340,9 +340,9 @@ export class Pattern {
|
||||
* silence
|
||||
* @noAutocomplete
|
||||
*/
|
||||
queryArc(begin, end) {
|
||||
queryArc(begin, end, controls = {}) {
|
||||
try {
|
||||
return this.query(new State(new TimeSpan(begin, end)));
|
||||
return this.query(new State(new TimeSpan(begin, end), controls));
|
||||
} catch (err) {
|
||||
logger(`[query]: ${err.message}`, 'error');
|
||||
return [];
|
||||
@@ -387,7 +387,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* As with {@link Pattern#withQuerySpan}, but the function is applied to both the
|
||||
* As with `withQuerySpan`, but the function is applied to both the
|
||||
* begin and end time of the query timespan.
|
||||
* @param {Function} func the function to apply
|
||||
* @returns Pattern
|
||||
@@ -398,7 +398,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link Pattern#withQuerySpan}, but the function is applied to the timespans
|
||||
* Similar to `withQuerySpan`, but the function is applied to the timespans
|
||||
* of all haps returned by pattern queries (both `part` timespans, and where
|
||||
* present, `whole` timespans).
|
||||
* @param {Function} func
|
||||
@@ -410,7 +410,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* As with {@link Pattern#withHapSpan}, but the function is applied to both the
|
||||
* As with `withHapSpan`, but the function is applied to both the
|
||||
* begin and end time of the hap timespans.
|
||||
* @param {Function} func the function to apply
|
||||
* @returns Pattern
|
||||
@@ -427,11 +427,11 @@ export class Pattern {
|
||||
* @noAutocomplete
|
||||
*/
|
||||
withHaps(func) {
|
||||
return new Pattern((state) => func(this.query(state)));
|
||||
return new Pattern((state) => func(this.query(state), state));
|
||||
}
|
||||
|
||||
/**
|
||||
* As with {@link Pattern#withHaps}, but applies the function to every hap, rather than every list of haps.
|
||||
* As with `withHaps`, but applies the function to every hap, rather than every list of haps.
|
||||
* @param {Function} func
|
||||
* @returns Pattern
|
||||
* @noAutocomplete
|
||||
@@ -499,7 +499,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* As with {@link Pattern#filterHaps}, but the function is applied to values
|
||||
* As with `filterHaps`, but the function is applied to values
|
||||
* inside haps.
|
||||
* @param {Function} value_test
|
||||
* @returns Pattern
|
||||
@@ -621,7 +621,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
/**
|
||||
* More human-readable version of the {@link Pattern#firstCycleValues} accessor.
|
||||
* More human-readable version of the `firstCycleValues` accessor.
|
||||
* @noAutocomplete
|
||||
*/
|
||||
get showFirstCycle() {
|
||||
@@ -691,13 +691,13 @@ export class Pattern {
|
||||
// Methods without corresponding toplevel functions
|
||||
|
||||
/**
|
||||
* Layers the result of the given function(s). Like {@link Pattern.superimpose}, but without the original pattern:
|
||||
* Layers the result of the given function(s). Like `superimpose`, but without the original pattern:
|
||||
* @name layer
|
||||
* @memberof Pattern
|
||||
* @synonyms apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
|
||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8"
|
||||
* .layer(x=>x.add("0,2"))
|
||||
* .scale('C minor').note()
|
||||
*/
|
||||
@@ -711,7 +711,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
|
||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8"
|
||||
* .superimpose(x=>x.add(2))
|
||||
* .scale('C minor').note()
|
||||
*/
|
||||
@@ -727,8 +727,8 @@ export class Pattern {
|
||||
* @name stack
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* s("hh*2").stack(
|
||||
* note("c2(3,8)")
|
||||
* s("hh*4").stack(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
stack(...pats) {
|
||||
@@ -745,8 +745,8 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @synonyms sequence, fastcat
|
||||
* @example
|
||||
* s("hh*2").seq(
|
||||
* note("c2(3,8)")
|
||||
* s("hh*4").seq(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
seq(...pats) {
|
||||
@@ -759,8 +759,8 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @synonyms slowcat
|
||||
* @example
|
||||
* s("hh*2").cat(
|
||||
* note("c2(3,8)")
|
||||
* s("hh*4").cat(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
cat(...pats) {
|
||||
@@ -858,7 +858,7 @@ Pattern.prototype.arpWith = function (func) {
|
||||
* Selects indices in in stacked notes.
|
||||
* @example
|
||||
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
|
||||
* .arp("0 [0,2] 1 [0,2]").slow(2)
|
||||
* .arp("0 [0,2] 1 [0,2]")
|
||||
* */
|
||||
Pattern.prototype.arp = function (pat) {
|
||||
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
|
||||
@@ -929,14 +929,14 @@ function _composeOp(a, b, func) {
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* // Here, the triad 0, 2, 4 is shifted by different amounts
|
||||
* "0 2 4".add("<0 3 4 0>").scale('C major').note()
|
||||
* n("0 2 4".add("<0 3 4 0>")).scale("C:major")
|
||||
* // Without add, the equivalent would be:
|
||||
* // "<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>".scale('C major').note()
|
||||
* // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major")
|
||||
* @example
|
||||
* // You can also use add with notes:
|
||||
* "c3 e3 g3".add("<0 5 7 0>").note()
|
||||
* note("c3 e3 g3".add("<0 5 7 0>"))
|
||||
* // Behind the scenes, the notes are converted to midi numbers:
|
||||
* // "48 52 55".add("<0 5 7 0>").note()
|
||||
* // note("48 52 55".add("<0 5 7 0>"))
|
||||
*/
|
||||
add: [numeralArgs((a, b) => a + b)], // support string concatenation
|
||||
/**
|
||||
@@ -945,7 +945,7 @@ function _composeOp(a, b, func) {
|
||||
* @name sub
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* "0 2 4".sub("<0 1 2 3>").scale('C4 minor').note()
|
||||
* n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor")
|
||||
* // See add for more information.
|
||||
*/
|
||||
sub: [numeralArgs((a, b) => a - b)],
|
||||
@@ -955,7 +955,7 @@ function _composeOp(a, b, func) {
|
||||
* @name mul
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* "1 1.5 [1.66, <2 2.33>]".mul(150).freq()
|
||||
* "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq()
|
||||
*/
|
||||
mul: [numeralArgs((a, b) => a * b)],
|
||||
/**
|
||||
@@ -1049,9 +1049,9 @@ function _composeOp(a, b, func) {
|
||||
* Applies the given structure to the pattern:
|
||||
*
|
||||
* @example
|
||||
* note("c3,eb3,g3")
|
||||
* note("c,eb,g")
|
||||
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
|
||||
* .slow(4)
|
||||
* .slow(2)
|
||||
*/
|
||||
Pattern.prototype.struct = function (...args) {
|
||||
return this.keepif.out(...args);
|
||||
@@ -1063,7 +1063,7 @@ function _composeOp(a, b, func) {
|
||||
* Returns silence when mask is 0 or "~"
|
||||
*
|
||||
* @example
|
||||
* note("c [eb,g] d [eb,g]").mask("<1 [0 1]>").slow(2)
|
||||
* note("c [eb,g] d [eb,g]").mask("<1 [0 1]>")
|
||||
*/
|
||||
Pattern.prototype.mask = function (...args) {
|
||||
return this.keepif.in(...args);
|
||||
@@ -1075,7 +1075,7 @@ function _composeOp(a, b, func) {
|
||||
* Resets the pattern to the start of the cycle for each onset of the reset pattern.
|
||||
*
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").reset("<x@3 x(3,8)>")
|
||||
* s("[<bd lt> sd]*2, hh*8").reset("<x@3 x(5,8)>")
|
||||
*/
|
||||
Pattern.prototype.reset = function (...args) {
|
||||
return this.keepif.trig(...args);
|
||||
@@ -1088,7 +1088,7 @@ function _composeOp(a, b, func) {
|
||||
* While reset will only reset the current cycle, restart will start from cycle 0.
|
||||
*
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").restart("<x@3 x(3,8)>")
|
||||
* s("[<bd lt> sd]*2, hh*8").restart("<x@3 x(5,8)>")
|
||||
*/
|
||||
Pattern.prototype.restart = function (...args) {
|
||||
return this.keepif.trigzero(...args);
|
||||
@@ -1178,7 +1178,8 @@ export function reify(thing) {
|
||||
* @return {Pattern}
|
||||
* @synonyms polyrhythm, pr
|
||||
* @example
|
||||
* stack("g3", "b3", ["e4", "d4"]).note() // "g3,b3,[e4,d4]".note()
|
||||
* stack("g3", "b3", ["e4", "d4"]).note()
|
||||
* // "g3,b3,[e4,d4]".note()
|
||||
*/
|
||||
export function stack(...pats) {
|
||||
// Array test here is to avoid infinite recursions..
|
||||
@@ -1189,7 +1190,7 @@ export function stack(...pats) {
|
||||
|
||||
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle:
|
||||
*
|
||||
* synonyms: {@link cat}
|
||||
* synonyms: `cat`
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
@@ -1237,17 +1238,19 @@ export function slowcatPrime(...pats) {
|
||||
* @synonyms slowcat
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* cat("e5", "b4", ["d5", "c5"]).note() // "<e5 b4 [d5 c5]>".note()
|
||||
* cat("e5", "b4", ["d5", "c5"]).note()
|
||||
* // "<e5 b4 [d5 c5]>".note()
|
||||
*
|
||||
*/
|
||||
export function cat(...pats) {
|
||||
return slowcat(...pats);
|
||||
}
|
||||
|
||||
/** Like {@link Pattern.seq}, but each step has a length, relative to the whole.
|
||||
/** Like `seq`, but each step has a length, relative to the whole.
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* timeCat([3,"e3"],[1, "g3"]).note() // "e3@3 g3".note()
|
||||
* timeCat([3,"e3"],[1, "g3"]).note()
|
||||
* // "e3@3 g3".note()
|
||||
*/
|
||||
export function timeCat(...timepats) {
|
||||
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
|
||||
@@ -1267,7 +1270,10 @@ export function timeCat(...timepats) {
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* arrange([4, "<c a f e>(3,8)"],[2, "<g a>(5,8)"]).note()
|
||||
* arrange(
|
||||
* [4, "<c a f e>(3,8)"],
|
||||
* [2, "<g a>(5,8)"]
|
||||
* ).note()
|
||||
*/
|
||||
export function arrange(...sections) {
|
||||
const total = sections.reduce((sum, [cycles]) => sum + cycles, 0);
|
||||
@@ -1279,7 +1285,7 @@ export function fastcat(...pats) {
|
||||
return slowcat(...pats)._fast(pats.length);
|
||||
}
|
||||
|
||||
/** See {@link fastcat} */
|
||||
/** See `fastcat` */
|
||||
export function sequence(...pats) {
|
||||
return fastcat(...pats);
|
||||
}
|
||||
@@ -1287,7 +1293,8 @@ export function sequence(...pats) {
|
||||
/** Like **cat**, but the items are crammed into one cycle.
|
||||
* @synonyms fastcat, sequence
|
||||
* @example
|
||||
* seq("e5", "b4", ["d5", "c5"]).note() // "e5 b4 [d5 c5]".note()
|
||||
* seq("e5", "b4", ["d5", "c5"]).note()
|
||||
* // "e5 b4 [d5 c5]".note()
|
||||
*
|
||||
*/
|
||||
export function seq(...pats) {
|
||||
@@ -1313,9 +1320,9 @@ function _sequenceCount(x) {
|
||||
* @param {number} steps how many items are placed in one cycle
|
||||
* @param {any[]} sequences one or more arrays of Patterns / values
|
||||
* @example
|
||||
* polymeterSteps(2, ["c", "d", "e", "f", "g", "f", "e", "d"])
|
||||
* .note().stack(s("bd")) // 1 cycle = 1 bd = 2 notes
|
||||
* // note("{c d e f g f e d}%2").stack(s("bd"))
|
||||
* polymeterSteps(4, ["c", "d", "e"])
|
||||
* .note().stack(s("bd"))
|
||||
* // note("{c d e}%4").stack(s("bd"))
|
||||
*/
|
||||
export function polymeterSteps(steps, ...args) {
|
||||
const seqs = args.map((a) => _sequenceCount(a));
|
||||
@@ -1463,7 +1470,7 @@ export function register(name, func, patternify = true) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "0.5 1.5 2.5".round().scale('C major').note()
|
||||
* n("0.5 1.5 2.5".round()).scale("C:major")
|
||||
*/
|
||||
export const round = register('round', function (pat) {
|
||||
return pat.asNumber().fmap((v) => Math.round(v));
|
||||
@@ -1477,7 +1484,7 @@ export const round = register('round', function (pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "42 42.1 42.5 43".floor().note()
|
||||
* note("42 42.1 42.5 43".floor())
|
||||
*/
|
||||
export const floor = register('floor', function (pat) {
|
||||
return pat.asNumber().fmap((v) => Math.floor(v));
|
||||
@@ -1491,7 +1498,7 @@ export const floor = register('floor', function (pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "42 42.1 42.5 43".ceil().note()
|
||||
* note("42 42.1 42.5 43".ceil())
|
||||
*/
|
||||
export const ceil = register('ceil', function (pat) {
|
||||
return pat.asNumber().fmap((v) => Math.ceil(v));
|
||||
@@ -1524,7 +1531,8 @@ export const fromBipolar = register('fromBipolar', function (pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4))
|
||||
* s("[bd sd]*2,hh*8")
|
||||
* .cutoff(sine.range(500,4000))
|
||||
*/
|
||||
export const range = register('range', function (min, max, pat) {
|
||||
return pat.mul(max - min).add(min);
|
||||
@@ -1538,7 +1546,8 @@ export const range = register('range', function (min, max, pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(sine.rangex(500,2000).slow(4))
|
||||
* s("[bd sd]*2,hh*8")
|
||||
* .cutoff(sine.rangex(500,4000))
|
||||
*/
|
||||
export const rangex = register('rangex', function (min, max, pat) {
|
||||
return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp);
|
||||
@@ -1551,7 +1560,8 @@ export const rangex = register('rangex', function (min, max, pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(sine2.range2(500,2000).slow(4))
|
||||
* s("[bd sd]*2,hh*8")
|
||||
* .cutoff(sine2.range2(500,4000))
|
||||
*/
|
||||
export const range2 = register('range2', function (min, max, pat) {
|
||||
return pat.fromBipolar()._range(min, max);
|
||||
@@ -1564,7 +1574,8 @@ export const range2 = register('range2', function (min, max, pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* ratio("1, 5:4, 3:2").mul(110).freq().s("piano").slow(2)
|
||||
* ratio("1, 5:4, 3:2").mul(110)
|
||||
* .freq().s("piano")
|
||||
*/
|
||||
export const ratio = register('ratio', (pat) =>
|
||||
pat.fmap((v) => {
|
||||
@@ -1636,7 +1647,7 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f
|
||||
});
|
||||
|
||||
/**
|
||||
* Similar to compress, but doesn't leave gaps, and the 'focus' can be bigger than a cycle
|
||||
* Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle
|
||||
* @example
|
||||
* s("bd hh sd hh").focus(1/4, 3/4)
|
||||
*/
|
||||
@@ -1667,7 +1678,7 @@ export const ply = register('ply', function (factor, pat) {
|
||||
* @param {number | Pattern} factor speed up factor
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
|
||||
* s("bd hh sd hh").fast(2) // s("[bd hh sd hh]*2")
|
||||
*/
|
||||
export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
|
||||
if (factor === 0) {
|
||||
@@ -1696,7 +1707,7 @@ export const hurry = register('hurry', function (r, pat) {
|
||||
* @param {number | Pattern} factor slow down factor
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
|
||||
* s("bd hh sd hh").slow(2) // s("[bd hh sd hh]/2")
|
||||
*/
|
||||
export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
|
||||
if (factor === 0) {
|
||||
@@ -1753,7 +1764,7 @@ export const lastOf = register('lastOf', function (n, func, pat) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* An alias for {@link firstOf}
|
||||
* An alias for `firstOf`
|
||||
* @name every
|
||||
* @memberof Pattern
|
||||
* @param {number} n how many cycles
|
||||
@@ -1785,9 +1796,9 @@ export const apply = register('apply', function (func, pat) {
|
||||
* @example
|
||||
* s("<bd sd>,hh*2").cpm(90) // = 90 bpm
|
||||
*/
|
||||
// TODO - global clock
|
||||
// this is redefined in repl.mjs, using the current cps as divisor
|
||||
export const cpm = register('cpm', function (cpm, pat) {
|
||||
return pat._fast(cpm / 60);
|
||||
return pat._fast(cpm / 60 / 1);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -1860,7 +1871,7 @@ export const linger = register('linger', function (t, pat) {
|
||||
* Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one.
|
||||
* @param {number} segments number of segments per cycle
|
||||
* @example
|
||||
* note(saw.range(0,12).segment(24)).add(40)
|
||||
* note(saw.range(40,52).segment(24))
|
||||
*/
|
||||
export const segment = register('segment', function (rate, pat) {
|
||||
return pat.struct(pure(true)._fast(rate));
|
||||
@@ -1886,7 +1897,7 @@ export const { invert, inv } = register(['invert', 'inv'], function (pat) {
|
||||
* @param {function} func
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)).note()
|
||||
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub("5")).note()
|
||||
*/
|
||||
export const when = register('when', function (on, func, pat) {
|
||||
return on ? func(pat) : pat;
|
||||
@@ -1923,7 +1934,7 @@ export const brak = register('brak', function (pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* note("c3 d3 e3 g3").rev()
|
||||
* note("c d e g").rev()
|
||||
*/
|
||||
export const rev = register('rev', function (pat) {
|
||||
const query = function (state) {
|
||||
@@ -1993,7 +2004,7 @@ export const palindrome = register('palindrome', function (pat) {
|
||||
* @name juxBy
|
||||
* @synonyms juxby
|
||||
* @example
|
||||
* s("lt ht mt ht hh").juxBy("<0 .5 1>/2", rev)
|
||||
* s("bd lt [~ ht] mt cp ~ bd hh").juxBy("<0 .5 1>/2", rev)
|
||||
*/
|
||||
export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, pat) {
|
||||
by /= 2;
|
||||
@@ -2012,7 +2023,11 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func,
|
||||
/**
|
||||
* The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel.
|
||||
* @example
|
||||
* s("lt ht mt ht hh").jux(rev)
|
||||
* s("bd lt [~ ht] mt cp ~ bd hh").jux(rev)
|
||||
* @example
|
||||
* s("bd lt [~ ht] mt cp ~ bd hh").jux(press)
|
||||
* @example
|
||||
* s("bd lt [~ ht] mt cp ~ bd hh").jux(iter(4))
|
||||
*/
|
||||
export const jux = register('jux', function (func, pat) {
|
||||
return pat._juxBy(1, func, pat);
|
||||
@@ -2028,7 +2043,7 @@ export const jux = register('jux', function (func, pat) {
|
||||
* @example
|
||||
* "<0 [2 4]>"
|
||||
* .echoWith(4, 1/8, (p,n) => p.add(n*2))
|
||||
* .scale('C minor').note().clip(.2)
|
||||
* .scale("C:minor").note()
|
||||
*/
|
||||
export const { echoWith, echowith, stutWith, stutwith } = register(
|
||||
['echoWith', 'echowith', 'stutWith', 'stutwith'],
|
||||
@@ -2121,7 +2136,8 @@ const { repeatCycles } = register('repeatCycles', _repeatCycles);
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note()
|
||||
* "0 1 2 3".chunk(4, x=>x.add(7))
|
||||
* .scale("A:minor").note()
|
||||
*/
|
||||
const _chunk = function (n, func, pat, back = false, fast = false) {
|
||||
const binary = Array(n - 1).fill(false);
|
||||
@@ -2146,7 +2162,8 @@ const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChu
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note()
|
||||
* "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);
|
||||
@@ -2160,10 +2177,11 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "<0 8> 1 2 3 4 5 6 7".fastChunk(4, x => x.color('red')).slow(4).scale("C2:major").note()
|
||||
.s("folkharp")
|
||||
* "<0 8> 1 2 3 4 5 6 7"
|
||||
* .fastChunk(4, x => x.color('red')).slow(2)
|
||||
* .scale("C2:major").note()
|
||||
*/
|
||||
const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
|
||||
export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
|
||||
return _chunk(n, func, pat, false, true);
|
||||
});
|
||||
|
||||
@@ -2178,6 +2196,8 @@ export const bypass = register('bypass', function (on, pat) {
|
||||
* @param {number} offset start point of loop in cycles
|
||||
* @param {number} cycles loop length in cycles
|
||||
* @example
|
||||
* note("<c d e f>").ribbon(1, 2).fast(2)
|
||||
* @example
|
||||
* // Looping a portion of randomness
|
||||
* note(irand(8).segment(4).scale('C3 minor')).ribbon(1337, 2)
|
||||
*/
|
||||
@@ -2251,7 +2271,7 @@ export const legato = register('legato', function (value, pat) {
|
||||
* s("rhodes")
|
||||
* .chop(4)
|
||||
* .rev() // reverse order of chops
|
||||
* .loopAt(4) // fit sample into 4 cycles
|
||||
* .loopAt(2) // fit sample into 2 cycles
|
||||
*
|
||||
*/
|
||||
export const chop = register('chop', function (n, pat) {
|
||||
@@ -2269,7 +2289,7 @@ export const chop = register('chop', function (n, pat) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("numbers:0 numbers:1 numbers:2").striate(6).slow(6)
|
||||
* s("numbers:0 numbers:1 numbers:2").striate(6).slow(3)
|
||||
*/
|
||||
export const striate = register('striate', function (n, pat) {
|
||||
const slices = Array.from({ length: n }, (x, i) => i);
|
||||
@@ -2285,10 +2305,10 @@ export const striate = register('striate', function (n, pat) {
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
||||
* s("rhodes").loopAt(4)
|
||||
* s("rhodes").loopAt(2)
|
||||
*/
|
||||
// TODO - global cps clock
|
||||
const _loopAt = function (factor, pat, cps = 1) {
|
||||
const _loopAt = function (factor, pat, cps = 0.5) {
|
||||
return pat
|
||||
.speed((1 / factor) * cps)
|
||||
.unit('c')
|
||||
@@ -2303,10 +2323,10 @@ const _loopAt = function (factor, pat, cps = 1) {
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
|
||||
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(0.75)
|
||||
* @example
|
||||
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
* s("breaks125/2").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
|
||||
* s("breaks125").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
|
||||
*/
|
||||
|
||||
export const slice = register(
|
||||
@@ -2334,50 +2354,51 @@ export const slice = register(
|
||||
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
* s("breaks165")
|
||||
* .splice(8, "0 1 [2 3 0]@2 3 0@2 7")
|
||||
* .hurry(0.65)
|
||||
*/
|
||||
|
||||
export const splice = register(
|
||||
'splice',
|
||||
function (npat, ipat, opat) {
|
||||
const sliced = slice(npat, ipat, opat);
|
||||
return sliced.withHap(function (hap) {
|
||||
return hap.withValue((v) => ({
|
||||
...{
|
||||
speed: (1 / v._slices / hap.whole.duration) * (v.speed || 1),
|
||||
unit: 'c',
|
||||
},
|
||||
...v,
|
||||
}));
|
||||
return new Pattern((state) => {
|
||||
// TODO - default cps to 0.5
|
||||
const cps = state.controls._cps || 1;
|
||||
const haps = sliced.query(state);
|
||||
return haps.map((hap) =>
|
||||
hap.withValue((v) => ({
|
||||
...{
|
||||
speed: (cps / v._slices / hap.whole.duration) * (v.speed || 1),
|
||||
unit: 'c',
|
||||
},
|
||||
...v,
|
||||
})),
|
||||
);
|
||||
});
|
||||
},
|
||||
false, // turns off auto-patternification
|
||||
);
|
||||
|
||||
// this function will be redefined in repl.mjs to use the correct cps value.
|
||||
// It is still here to work in cases where repl.mjs is not used
|
||||
|
||||
export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
|
||||
return _loopAt(factor, pat, 1);
|
||||
return new Pattern((state) => _loopAt(factor, pat, state.controls._cps).query(state));
|
||||
});
|
||||
|
||||
// the fit function will be redefined in repl.mjs to use the correct cps value.
|
||||
// It is still here to work in cases where repl.mjs is not used
|
||||
/**
|
||||
* Makes the sample fit its event duration. Good for rhythmical loops like drum breaks.
|
||||
* Similar to loopAt.
|
||||
* Similar to `loopAt`.
|
||||
* @name fit
|
||||
* @example
|
||||
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
||||
* s("rhodes/4").fit()
|
||||
* s("rhodes/2").fit()
|
||||
*/
|
||||
export const fit = register('fit', (pat) =>
|
||||
pat.withHap((hap) =>
|
||||
hap.withValue((v) => ({
|
||||
...v,
|
||||
speed: 1 / hap.whole.duration,
|
||||
unit: 'c',
|
||||
})),
|
||||
pat.withHaps((haps, state) =>
|
||||
haps.map((hap) =>
|
||||
hap.withValue((v) => ({
|
||||
...v,
|
||||
speed: (state.controls._cps || 1) / hap.whole.duration,
|
||||
unit: 'c',
|
||||
})),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -61,12 +61,67 @@ export function repl({
|
||||
scheduler.setPattern(pattern, autostart);
|
||||
};
|
||||
setTime(() => scheduler.now()); // TODO: refactor?
|
||||
|
||||
const stop = () => scheduler.stop();
|
||||
const start = () => scheduler.start();
|
||||
const pause = () => scheduler.pause();
|
||||
const toggle = () => scheduler.toggle();
|
||||
const setCps = (cps) => scheduler.setCps(cps);
|
||||
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
||||
const all = function (transform) {
|
||||
allTransform = transform;
|
||||
return silence;
|
||||
};
|
||||
|
||||
// set pattern methods that use this repl via closure
|
||||
const injectPatternMethods = () => {
|
||||
Pattern.prototype.p = function (id) {
|
||||
pPatterns[id] = this;
|
||||
return this;
|
||||
};
|
||||
Pattern.prototype.q = function (id) {
|
||||
return silence;
|
||||
};
|
||||
try {
|
||||
for (let i = 1; i < 10; ++i) {
|
||||
Object.defineProperty(Pattern.prototype, `d${i}`, {
|
||||
get() {
|
||||
return this.p(i);
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(Pattern.prototype, `p${i}`, {
|
||||
get() {
|
||||
return this.p(i);
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Pattern.prototype[`q${i}`] = silence;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('injectPatternMethods: error:', err);
|
||||
}
|
||||
const cpm = register('cpm', function (cpm, pat) {
|
||||
return pat._fast(cpm / 60 / scheduler.cps);
|
||||
});
|
||||
evalScope({
|
||||
all,
|
||||
hush,
|
||||
cpm,
|
||||
setCps,
|
||||
setcps: setCps,
|
||||
setCpm,
|
||||
setcpm: setCpm,
|
||||
});
|
||||
};
|
||||
|
||||
const evaluate = async (code, autostart = true, shouldHush = true) => {
|
||||
if (!code) {
|
||||
throw new Error('no code to evaluate');
|
||||
}
|
||||
try {
|
||||
updateState({ code, pending: true });
|
||||
injectPatternMethods();
|
||||
await beforeEval?.({ code });
|
||||
shouldHush && hush();
|
||||
let { pattern, meta } = await _evaluate(code, transpiler);
|
||||
@@ -94,74 +149,11 @@ export function repl({
|
||||
afterEval?.({ code, pattern, meta });
|
||||
return pattern;
|
||||
} catch (err) {
|
||||
// console.warn(`[repl] eval error: ${err.message}`);
|
||||
logger(`[eval] error: ${err.message}`, 'error');
|
||||
updateState({ evalError: err, pending: false });
|
||||
onEvalError?.(err);
|
||||
}
|
||||
};
|
||||
const stop = () => scheduler.stop();
|
||||
const start = () => scheduler.start();
|
||||
const pause = () => scheduler.pause();
|
||||
const toggle = () => scheduler.toggle();
|
||||
const setCps = (cps) => scheduler.setCps(cps);
|
||||
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
||||
|
||||
// the following functions use the cps value, which is why they are defined here..
|
||||
const loopAt = register('loopAt', (cycles, pat) => {
|
||||
return pat.loopAtCps(cycles, scheduler.cps);
|
||||
});
|
||||
|
||||
Pattern.prototype.p = function (id) {
|
||||
pPatterns[id] = this;
|
||||
return this;
|
||||
};
|
||||
Pattern.prototype.q = function (id) {
|
||||
return silence;
|
||||
};
|
||||
|
||||
const all = function (transform) {
|
||||
allTransform = transform;
|
||||
return silence;
|
||||
};
|
||||
try {
|
||||
for (let i = 1; i < 10; ++i) {
|
||||
Object.defineProperty(Pattern.prototype, `d${i}`, {
|
||||
get() {
|
||||
return this.p(i);
|
||||
},
|
||||
});
|
||||
Object.defineProperty(Pattern.prototype, `p${i}`, {
|
||||
get() {
|
||||
return this.p(i);
|
||||
},
|
||||
});
|
||||
Pattern.prototype[`q${i}`] = silence;
|
||||
}
|
||||
} catch (err) {
|
||||
// already defined..
|
||||
}
|
||||
|
||||
const fit = register('fit', (pat) =>
|
||||
pat.withHap((hap) =>
|
||||
hap.withValue((v) => ({
|
||||
...v,
|
||||
speed: scheduler.cps / hap.whole.duration, // overwrite speed completely?
|
||||
unit: 'c',
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
evalScope({
|
||||
loopAt,
|
||||
fit,
|
||||
all,
|
||||
hush,
|
||||
setCps,
|
||||
setcps: setCps,
|
||||
setCpm,
|
||||
setcpm: setCpm,
|
||||
});
|
||||
const setCode = (code) => updateState({ code });
|
||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
import { Hap } from './hap.mjs';
|
||||
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
import { id, _mod, clamp } from './util.mjs';
|
||||
import { id, _mod, clamp, objectMap } from './util.mjs';
|
||||
|
||||
export function steady(value) {
|
||||
// A continuous value
|
||||
@@ -27,9 +27,11 @@ export const isaw2 = isaw.toBipolar();
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* "c3 [eb3,g3] g2 [g3,bb3]".note().clip(saw.slow(4))
|
||||
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
|
||||
* .clip(saw.slow(2))
|
||||
* @example
|
||||
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
|
||||
* n(saw.range(0,8).segment(8))
|
||||
* .scale('C major')
|
||||
*
|
||||
*/
|
||||
export const saw = signal((t) => t % 1);
|
||||
@@ -42,7 +44,8 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* sine.segment(16).range(0,15).slow(2).scale('C minor').note()
|
||||
* n(sine.segment(16).range(0,15))
|
||||
* .scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const sine = sine2.fromBipolar();
|
||||
@@ -52,7 +55,8 @@ export const sine = sine2.fromBipolar();
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor').note()
|
||||
* n(stack(sine,cosine).segment(16).range(0,15))
|
||||
* .scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const cosine = sine._early(Fraction(1).div(4));
|
||||
@@ -63,7 +67,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* square.segment(2).range(0,7).scale('C minor').note()
|
||||
* n(square.segment(4).range(0,7)).scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const square = signal((t) => Math.floor((t * 2) % 2));
|
||||
@@ -74,7 +78,7 @@ export const square2 = square.toBipolar();
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* tri.segment(8).range(0,7).scale('C minor').note()
|
||||
* n(tri.segment(8).range(0,7)).scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const tri = fastcat(isaw, saw);
|
||||
@@ -118,8 +122,8 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
||||
/**
|
||||
* A discrete pattern of numbers from 0 to n-1
|
||||
* @example
|
||||
* run(4).scale('C4 major').note()
|
||||
* // "0 1 2 3".scale('C4 major').note()
|
||||
* n(run(4)).scale("C4:pentatonic")
|
||||
* // n("0 1 2 3").scale("C4:pentatonic")
|
||||
*/
|
||||
export const run = (n) => saw.range(0, n).floor().segment(n);
|
||||
|
||||
@@ -129,7 +133,7 @@ export const run = (n) => saw.range(0, n).floor().segment(n);
|
||||
* @name rand
|
||||
* @example
|
||||
* // randomly change the cutoff
|
||||
* s("bd sd,hh*4").cutoff(rand.range(500,2000))
|
||||
* s("bd*4,hh*8").cutoff(rand.range(500,8000))
|
||||
*
|
||||
*/
|
||||
export const rand = signal(timeToRand);
|
||||
@@ -151,36 +155,127 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
|
||||
* @param {number} n max value (exclusive)
|
||||
* @example
|
||||
* // randomly select scale notes from 0 - 7 (= C to C)
|
||||
* irand(8).struct("x(3,8)").scale('C minor').note()
|
||||
* n(irand(8)).struct("x x*2 x x*3").scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
|
||||
|
||||
/**
|
||||
* pick from the list of values (or patterns of values) via the index using the given
|
||||
* pattern of integers
|
||||
const _pick = function (lookup, pat, modulo = true) {
|
||||
const array = Array.isArray(lookup);
|
||||
const len = Object.keys(lookup).length;
|
||||
|
||||
lookup = objectMap(lookup, reify);
|
||||
|
||||
if (len === 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat.fmap((i) => {
|
||||
let key = i;
|
||||
if (array) {
|
||||
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
|
||||
}
|
||||
return lookup[key];
|
||||
});
|
||||
};
|
||||
|
||||
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `inhabit`, but maintains the structure of the original patterns.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* note(pick("<0 1 [2!2] 3>", ["g a", "e f", "f g f g" , "g a c d"]))
|
||||
* note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"]))
|
||||
* @example
|
||||
* sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"]))
|
||||
* @example
|
||||
* sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"]))
|
||||
* @example
|
||||
* s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
|
||||
*/
|
||||
|
||||
export const pick = (pat, xs) => {
|
||||
xs = xs.map(reify);
|
||||
if (xs.length == 0) {
|
||||
return silence;
|
||||
export const pick = function (lookup, pat) {
|
||||
// backward compatibility - the args used to be flipped
|
||||
if (Array.isArray(pat)) {
|
||||
[pat, lookup] = [lookup, pat];
|
||||
}
|
||||
return pat
|
||||
.fmap((i) => {
|
||||
const key = clamp(Math.round(i), 0, xs.length - 1);
|
||||
return xs[key];
|
||||
})
|
||||
.innerJoin();
|
||||
return __pick(lookup, pat);
|
||||
};
|
||||
|
||||
const __pick = register('pick', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).innerJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pick`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* For example, if you pick the fifth pattern of a list of three, you'll get the
|
||||
* second one.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const pickmod = register('pickmod', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).innerJoin();
|
||||
});
|
||||
|
||||
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
|
||||
* @param {Pattern} pat
|
||||
* @param {Pattern} lookup a pattern of indices
|
||||
* @param {function[]} funcs the array of functions from which to pull
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
|
||||
* @example
|
||||
* note("<c2 d2>(3,8)").s("square")
|
||||
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
|
||||
*/
|
||||
export const pickF = register('pickF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pick(lookup, funcs));
|
||||
});
|
||||
|
||||
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {Pattern} lookup a pattern of indices
|
||||
* @param {function[]} funcs the array of functions from which to pull
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pickmod(lookup, funcs));
|
||||
});
|
||||
|
||||
/**
|
||||
* pick from the list of values (or patterns of values) via the index using the given
|
||||
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
|
||||
b: s("cp sd")
|
||||
})
|
||||
* @example
|
||||
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
|
||||
*/
|
||||
export const inhabit = register('inhabit', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).squeezeJoin();
|
||||
});
|
||||
|
||||
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* For example, if you pick the fifth pattern of a list of three, you'll get the
|
||||
* second one.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const inhabitmod = register('inhabit', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).squeezeJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
* Pick from the list of values (or patterns of values) via the index using the given
|
||||
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
@@ -269,9 +364,9 @@ Pattern.prototype.choose2 = function (...xs) {
|
||||
* Picks one of the elements at random each cycle.
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* chooseCycles("bd", "hh", "sd").s().fast(4)
|
||||
* chooseCycles("bd", "hh", "sd").s().fast(8)
|
||||
* @example
|
||||
* "bd | hh | sd".s().fast(4)
|
||||
* s("bd | hh | sd").fast(8)
|
||||
*/
|
||||
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
|
||||
|
||||
@@ -314,7 +409,7 @@ export const perlinWith = (pat) => {
|
||||
* @name perlin
|
||||
* @example
|
||||
* // randomly change the cutoff
|
||||
* s("bd sd,hh*4").cutoff(perlin.range(500,2000))
|
||||
* s("bd*4,hh*8").cutoff(perlin.range(500,8000))
|
||||
*
|
||||
*/
|
||||
export const perlin = perlinWith(time.fmap((v) => Number(v)));
|
||||
@@ -356,7 +451,7 @@ export const degradeBy = register('degradeBy', function (x, pat) {
|
||||
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
|
||||
|
||||
/**
|
||||
* Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
|
||||
* Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount.
|
||||
* 0 = 100% chance of removal
|
||||
* 1 = 0% chance of removal
|
||||
* Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
|
||||
@@ -380,7 +475,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
|
||||
/**
|
||||
*
|
||||
* Randomly applies the given function by the given probability.
|
||||
* Similar to {@link Pattern#someCyclesBy}
|
||||
* Similar to `someCyclesBy`
|
||||
*
|
||||
* @name sometimesBy
|
||||
* @memberof Pattern
|
||||
@@ -388,7 +483,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
|
||||
* s("hh*8").sometimesBy(.4, x=>x.speed("0.5"))
|
||||
*/
|
||||
|
||||
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
|
||||
@@ -406,7 +501,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*4").sometimes(x=>x.speed("0.5"))
|
||||
* s("hh*8").sometimes(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const sometimes = register('sometimes', function (func, pat) {
|
||||
return pat._sometimesBy(0.5, func);
|
||||
@@ -415,7 +510,7 @@ export const sometimes = register('sometimes', function (func, pat) {
|
||||
/**
|
||||
*
|
||||
* Randomly applies the given function by the given probability on a cycle by cycle basis.
|
||||
* Similar to {@link Pattern#sometimesBy}
|
||||
* Similar to `sometimesBy`
|
||||
*
|
||||
* @name someCyclesBy
|
||||
* @memberof Pattern
|
||||
@@ -423,7 +518,7 @@ export const sometimes = register('sometimes', function (func, pat) {
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
|
||||
* s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5"))
|
||||
*/
|
||||
|
||||
export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
|
||||
@@ -445,7 +540,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat)
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
|
||||
* s("bd,hh*8").someCycles(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const someCycles = register('someCycles', function (func, pat) {
|
||||
return pat._someCyclesBy(0.5, func);
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
rev,
|
||||
time,
|
||||
run,
|
||||
pick,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
@@ -1057,4 +1058,63 @@ describe('Pattern', () => {
|
||||
expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([0, 0, 1, 1, 0, 0]);
|
||||
});
|
||||
});
|
||||
describe('inhabit', () => {
|
||||
it('Can pattern named patterns', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence('a', 'b', stack('a', 'b')).inhabit({ a: sequence(1, 2), b: sequence(10, 20, 30) }),
|
||||
sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('Can pattern indexed patterns', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence('0', '1', stack('0', '1')).inhabit([sequence(1, 2), sequence(10, 20, 30)]),
|
||||
sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('pick', () => {
|
||||
it('Can pattern named patterns', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence('a', 'b', 'a', stack('a', 'b')).pick({ a: sequence(1, 2, 3, 4), b: sequence(10, 20, 30, 40) }),
|
||||
sequence(1, 20, 3, stack(4, 40)),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('Can pattern indexed patterns', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(0, 1, 0, stack(0, 1)).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]),
|
||||
sequence(1, 20, 3, stack(4, 40)),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('Clamps indexes', () => {
|
||||
expect(
|
||||
sameFirst(sequence(0, 1, 2, 3).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), sequence(1, 20, 30, 40)),
|
||||
);
|
||||
});
|
||||
it('Is backwards compatible', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
pick([sequence('a', 'b'), sequence('c', 'd')], sequence(0, 1)),
|
||||
pick(sequence(0, 1), [sequence('a', 'b'), sequence('c', 'd')]),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('pickmod', () => {
|
||||
it('Wraps indexes', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(0, 1, 2, 3).pickmod([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]),
|
||||
sequence(1, 20, 3, 40),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -316,3 +316,10 @@ export function hash2code(hash) {
|
||||
return base64ToUnicode(decodeURIComponent(hash));
|
||||
//return atob(decodeURIComponent(codeParam || ''));
|
||||
}
|
||||
|
||||
export function objectMap(obj, fn) {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(fn);
|
||||
}
|
||||
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/csound",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/embed",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
||||
"main": "embed.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/hydra",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Hydra integration for strudel",
|
||||
"main": "hydra.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/midi",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -288,48 +288,50 @@ function peg$parse(input, options) {
|
||||
|
||||
var peg$f0 = function() { return parseFloat(text()); };
|
||||
var peg$f1 = function() { return parseInt(text()); };
|
||||
var peg$f2 = function(chars) { return new AtomStub(chars.join("")) };
|
||||
var peg$f3 = function(s) { return s };
|
||||
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
|
||||
var peg$f5 = function(a) { return a };
|
||||
var peg$f6 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
|
||||
var peg$f7 = function(a) { return x => x.options_['weight'] = a };
|
||||
var peg$f8 = function(a) { return x => x.options_['reps'] = a };
|
||||
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
|
||||
var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) };
|
||||
var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) };
|
||||
var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) };
|
||||
var peg$f13 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) };
|
||||
var peg$f14 = function(s) { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) };
|
||||
var peg$f15 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
|
||||
var peg$f2 = function(chars) { const s = chars.join(""); return (s === ".") || (s === "_") };
|
||||
var peg$f3 = function(chars) { return new AtomStub(chars.join("")) };
|
||||
var peg$f4 = function(s) { return s };
|
||||
var peg$f5 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
|
||||
var peg$f6 = function(a) { return a };
|
||||
var peg$f7 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
|
||||
var peg$f8 = function(a) { return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 };
|
||||
var peg$f9 = function(a) { return x => x.options_['reps'] = (x.options_['reps'] ?? 1) + (a ?? 2) - 1 };
|
||||
var peg$f10 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
|
||||
var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) };
|
||||
var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) };
|
||||
var peg$f13 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) };
|
||||
var peg$f14 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) };
|
||||
var peg$f15 = function(s) { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) };
|
||||
var peg$f16 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
|
||||
for (const op of ops) {
|
||||
op(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
var peg$f16 = function(s) { return new PatternStub(s, 'fastcat'); };
|
||||
var peg$f17 = function(tail) { return { alignment: 'stack', list: tail }; };
|
||||
var peg$f18 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; };
|
||||
var peg$f19 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } };
|
||||
var peg$f20 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); };
|
||||
var peg$f21 = function(sc) { return sc; };
|
||||
var peg$f22 = function(s) { return { name: "struct", args: { mini:s }}};
|
||||
var peg$f23 = function(s) { return { name: "target", args : { name:s}}};
|
||||
var peg$f24 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}};
|
||||
var peg$f25 = function(a) { return { name: "stretch", args :{ amount: a}}};
|
||||
var peg$f26 = function(a) { return { name: "shift", args :{ amount: "-"+a}}};
|
||||
var peg$f27 = function(a) { return { name: "shift", args :{ amount: a}}};
|
||||
var peg$f28 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}};
|
||||
var peg$f29 = function(s) { return { name: "scale", args :{ scale: s.join("")}}};
|
||||
var peg$f30 = function(s, v) { return v};
|
||||
var peg$f31 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); };
|
||||
var peg$f32 = function(sg) {return sg};
|
||||
var peg$f33 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)};
|
||||
var peg$f34 = function(sc) { return sc };
|
||||
var peg$f35 = function(c) { return c };
|
||||
var peg$f36 = function(v) { return new CommandStub("setcps", { value: v})};
|
||||
var peg$f37 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})};
|
||||
var peg$f38 = function() { return new CommandStub("hush")};
|
||||
var peg$f17 = function(s) { return new PatternStub(s, 'fastcat'); };
|
||||
var peg$f18 = function(tail) { return { alignment: 'stack', list: tail }; };
|
||||
var peg$f19 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; };
|
||||
var peg$f20 = function(tail) { return { alignment: 'feet', list: tail, seed: seed++ }; };
|
||||
var peg$f21 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } };
|
||||
var peg$f22 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); };
|
||||
var peg$f23 = function(sc) { return sc; };
|
||||
var peg$f24 = function(s) { return { name: "struct", args: { mini:s }}};
|
||||
var peg$f25 = function(s) { return { name: "target", args : { name:s}}};
|
||||
var peg$f26 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}};
|
||||
var peg$f27 = function(a) { return { name: "stretch", args :{ amount: a}}};
|
||||
var peg$f28 = function(a) { return { name: "shift", args :{ amount: "-"+a}}};
|
||||
var peg$f29 = function(a) { return { name: "shift", args :{ amount: a}}};
|
||||
var peg$f30 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}};
|
||||
var peg$f31 = function(s) { return { name: "scale", args :{ scale: s.join("")}}};
|
||||
var peg$f32 = function(s, v) { return v};
|
||||
var peg$f33 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); };
|
||||
var peg$f34 = function(sg) {return sg};
|
||||
var peg$f35 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)};
|
||||
var peg$f36 = function(sc) { return sc };
|
||||
var peg$f37 = function(c) { return c };
|
||||
var peg$f38 = function(v) { return new CommandStub("setcps", { value: v})};
|
||||
var peg$f39 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})};
|
||||
var peg$f40 = function() { return new CommandStub("hush")};
|
||||
var peg$currPos = 0;
|
||||
var peg$savedPos = 0;
|
||||
var peg$posDetailsCache = [{ line: 1, column: 1 }];
|
||||
@@ -821,6 +823,30 @@ function peg$parse(input, options) {
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parsedot() {
|
||||
var s0, s1, s2, s3;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parsews();
|
||||
if (input.charCodeAt(peg$currPos) === 46) {
|
||||
s2 = peg$c0;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e1); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parsews();
|
||||
s1 = [s1, s2, s3];
|
||||
s0 = s1;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parsequote() {
|
||||
var s0;
|
||||
|
||||
@@ -913,7 +939,7 @@ function peg$parse(input, options) {
|
||||
}
|
||||
|
||||
function peg$parsestep() {
|
||||
var s0, s1, s2, s3;
|
||||
var s0, s1, s2, s3, s4;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parsews();
|
||||
@@ -929,8 +955,20 @@ function peg$parse(input, options) {
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parsews();
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f2(s2);
|
||||
peg$savedPos = peg$currPos;
|
||||
s4 = peg$f2(s2);
|
||||
if (s4) {
|
||||
s4 = peg$FAILED;
|
||||
} else {
|
||||
s4 = undefined;
|
||||
}
|
||||
if (s4 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f3(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -966,7 +1004,7 @@ function peg$parse(input, options) {
|
||||
if (s6 !== peg$FAILED) {
|
||||
s7 = peg$parsews();
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f3(s4);
|
||||
s0 = peg$f4(s4);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1014,7 +1052,7 @@ function peg$parse(input, options) {
|
||||
}
|
||||
s8 = peg$parsews();
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f4(s4, s7);
|
||||
s0 = peg$f5(s4, s7);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1046,7 +1084,7 @@ function peg$parse(input, options) {
|
||||
s2 = peg$parseslice();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f5(s2);
|
||||
s0 = peg$f6(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1086,7 +1124,7 @@ function peg$parse(input, options) {
|
||||
if (s6 !== peg$FAILED) {
|
||||
s7 = peg$parsews();
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f6(s4);
|
||||
s0 = peg$f7(s4);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1150,25 +1188,33 @@ function peg$parse(input, options) {
|
||||
}
|
||||
|
||||
function peg$parseop_weight() {
|
||||
var s0, s1, s2;
|
||||
var s0, s1, s2, s3;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parsews();
|
||||
if (input.charCodeAt(peg$currPos) === 64) {
|
||||
s1 = peg$c18;
|
||||
s2 = peg$c18;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e26); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parsenumber();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f7(s2);
|
||||
if (s2 === peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 95) {
|
||||
s2 = peg$c10;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
||||
}
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 === peg$FAILED) {
|
||||
s3 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f8(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1178,25 +1224,24 @@ function peg$parse(input, options) {
|
||||
}
|
||||
|
||||
function peg$parseop_replicate() {
|
||||
var s0, s1, s2;
|
||||
var s0, s1, s2, s3;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parsews();
|
||||
if (input.charCodeAt(peg$currPos) === 33) {
|
||||
s1 = peg$c19;
|
||||
s2 = peg$c19;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e27); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parsenumber();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f8(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 === peg$FAILED) {
|
||||
s3 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f9(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1246,7 +1291,7 @@ function peg$parse(input, options) {
|
||||
}
|
||||
if (s13 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f9(s3, s7, s11);
|
||||
s0 = peg$f10(s3, s7, s11);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1286,7 +1331,7 @@ function peg$parse(input, options) {
|
||||
s2 = peg$parseslice();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f10(s2);
|
||||
s0 = peg$f11(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1314,7 +1359,7 @@ function peg$parse(input, options) {
|
||||
s2 = peg$parseslice();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f11(s2);
|
||||
s0 = peg$f12(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1344,7 +1389,7 @@ function peg$parse(input, options) {
|
||||
s2 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f12(s2);
|
||||
s0 = peg$f13(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1368,7 +1413,7 @@ function peg$parse(input, options) {
|
||||
s2 = peg$parseslice();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f13(s2);
|
||||
s0 = peg$f14(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1396,7 +1441,7 @@ function peg$parse(input, options) {
|
||||
s2 = peg$parseslice();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f14(s2);
|
||||
s0 = peg$f15(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1422,7 +1467,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parseslice_op();
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f15(s1, s2);
|
||||
s0 = peg$f16(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1447,7 +1492,7 @@ function peg$parse(input, options) {
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f16(s1);
|
||||
s1 = peg$f17(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
@@ -1496,7 +1541,7 @@ function peg$parse(input, options) {
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f17(s1);
|
||||
s1 = peg$f18(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
@@ -1545,7 +1590,56 @@ function peg$parse(input, options) {
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f18(s1);
|
||||
s1 = peg$f19(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parsedot_tail() {
|
||||
var s0, s1, s2, s3, s4;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = [];
|
||||
s2 = peg$currPos;
|
||||
s3 = peg$parsedot();
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parsesequence();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s2 = s4;
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
while (s2 !== peg$FAILED) {
|
||||
s1.push(s2);
|
||||
s2 = peg$currPos;
|
||||
s3 = peg$parsedot();
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parsesequence();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s2 = s4;
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f20(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
@@ -1561,12 +1655,15 @@ function peg$parse(input, options) {
|
||||
s2 = peg$parsestack_tail();
|
||||
if (s2 === peg$FAILED) {
|
||||
s2 = peg$parsechoose_tail();
|
||||
if (s2 === peg$FAILED) {
|
||||
s2 = peg$parsedot_tail();
|
||||
}
|
||||
}
|
||||
if (s2 === peg$FAILED) {
|
||||
s2 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f19(s1, s2);
|
||||
s0 = peg$f21(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1586,7 +1683,7 @@ function peg$parse(input, options) {
|
||||
s2 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f20(s1, s2);
|
||||
s0 = peg$f22(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1609,7 +1706,7 @@ function peg$parse(input, options) {
|
||||
s6 = peg$parsequote();
|
||||
if (s6 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f21(s4);
|
||||
s0 = peg$f23(s4);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1671,7 +1768,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parsemini_or_operator();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f22(s3);
|
||||
s0 = peg$f24(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1704,7 +1801,7 @@ function peg$parse(input, options) {
|
||||
s5 = peg$parsequote();
|
||||
if (s5 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f23(s4);
|
||||
s0 = peg$f25(s4);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1749,7 +1846,7 @@ function peg$parse(input, options) {
|
||||
s7 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f24(s3, s5, s7);
|
||||
s0 = peg$f26(s3, s5, s7);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1782,7 +1879,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f25(s3);
|
||||
s0 = peg$f27(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1811,7 +1908,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f26(s3);
|
||||
s0 = peg$f28(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1840,7 +1937,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f27(s3);
|
||||
s0 = peg$f29(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1869,7 +1966,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f28(s3);
|
||||
s0 = peg$f30(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -1911,7 +2008,7 @@ function peg$parse(input, options) {
|
||||
s5 = peg$parsequote();
|
||||
if (s5 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f29(s4);
|
||||
s0 = peg$f31(s4);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -2003,7 +2100,7 @@ function peg$parse(input, options) {
|
||||
s9 = peg$parsemini_or_operator();
|
||||
if (s9 !== peg$FAILED) {
|
||||
peg$savedPos = s7;
|
||||
s7 = peg$f30(s5, s9);
|
||||
s7 = peg$f32(s5, s9);
|
||||
} else {
|
||||
peg$currPos = s7;
|
||||
s7 = peg$FAILED;
|
||||
@@ -2020,7 +2117,7 @@ function peg$parse(input, options) {
|
||||
s9 = peg$parsemini_or_operator();
|
||||
if (s9 !== peg$FAILED) {
|
||||
peg$savedPos = s7;
|
||||
s7 = peg$f30(s5, s9);
|
||||
s7 = peg$f32(s5, s9);
|
||||
} else {
|
||||
peg$currPos = s7;
|
||||
s7 = peg$FAILED;
|
||||
@@ -2040,7 +2137,7 @@ function peg$parse(input, options) {
|
||||
}
|
||||
if (s8 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f31(s5, s6);
|
||||
s0 = peg$f33(s5, s6);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -2086,7 +2183,7 @@ function peg$parse(input, options) {
|
||||
s4 = peg$parsecomment();
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f32(s1);
|
||||
s0 = peg$f34(s1);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -2108,7 +2205,7 @@ function peg$parse(input, options) {
|
||||
s5 = peg$parsemini_or_operator();
|
||||
if (s5 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f33(s1, s5);
|
||||
s0 = peg$f35(s1, s5);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -2133,7 +2230,7 @@ function peg$parse(input, options) {
|
||||
s1 = peg$parsemini_or_operator();
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f34(s1);
|
||||
s1 = peg$f36(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
if (s0 === peg$FAILED) {
|
||||
@@ -2166,7 +2263,7 @@ function peg$parse(input, options) {
|
||||
if (s2 !== peg$FAILED) {
|
||||
s3 = peg$parsews();
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f35(s2);
|
||||
s0 = peg$f37(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -2191,7 +2288,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f36(s3);
|
||||
s0 = peg$f38(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -2220,7 +2317,7 @@ function peg$parse(input, options) {
|
||||
s3 = peg$parsenumber();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f37(s3);
|
||||
s0 = peg$f39(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
@@ -2246,7 +2343,7 @@ function peg$parse(input, options) {
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f38();
|
||||
s1 = peg$f40();
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ DIGIT = [0-9]
|
||||
ws "whitespace" = [ \n\r\t\u00A0]*
|
||||
comma = ws "," ws
|
||||
pipe = ws "|" ws
|
||||
dot = ws "." ws
|
||||
quote = '"' / "'"
|
||||
|
||||
// ------------------ steps and cycles ---------------------------
|
||||
@@ -105,7 +106,8 @@ quote = '"' / "'"
|
||||
// single step definition (e.g bd)
|
||||
step_char "a letter, a number, \"-\", \"#\", \".\", \"^\", \"_\"" =
|
||||
unicode_letter / [0-9~] / "-" / "#" / "." / "^" / "_"
|
||||
step = ws chars:step_char+ ws { return new AtomStub(chars.join("")) }
|
||||
|
||||
step = ws chars:step_char+ ws !{ const s = chars.join(""); return (s === ".") || (s === "_") } { return new AtomStub(chars.join("")) }
|
||||
|
||||
// define a sub cycle e.g. [1 2, 3 [4]]
|
||||
sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s }
|
||||
@@ -129,11 +131,11 @@ slice = step / sub_cycle / polymeter / slow_sequence
|
||||
// at this point, we assume we can represent them as regular sequence operators
|
||||
slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail / op_range
|
||||
|
||||
op_weight = "@" a:number
|
||||
{ return x => x.options_['weight'] = a }
|
||||
op_weight = ws ("@" / "_") a:number?
|
||||
{ return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 }
|
||||
|
||||
op_replicate = "!"a:number
|
||||
{ return x => x.options_['reps'] = a }
|
||||
op_replicate = ws "!" a:number?
|
||||
{ return x => x.options_['reps'] = (x.options_['reps'] ?? 1) + (a ?? 2) - 1 }
|
||||
|
||||
op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")"
|
||||
{ return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }
|
||||
@@ -175,9 +177,13 @@ stack_tail = tail:(comma @sequence)+
|
||||
choose_tail = tail:(pipe @sequence)+
|
||||
{ return { alignment: 'rand', list: tail, seed: seed++ }; }
|
||||
|
||||
// a foot separates subsequences, as an alternative to wrapping them in []
|
||||
dot_tail = tail:(dot @sequence)+
|
||||
{ return { alignment: 'feet', list: tail, seed: seed++ }; }
|
||||
|
||||
// if the stack contains only one element, we don't create a stack but return the
|
||||
// underlying element
|
||||
stack_or_choose = head:sequence tail:(stack_tail / choose_tail)?
|
||||
stack_or_choose = head:sequence tail:(stack_tail / choose_tail / dot_tail)?
|
||||
{ if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } }
|
||||
|
||||
polymeter_stack = head:sequence tail:stack_tail?
|
||||
@@ -287,4 +293,4 @@ Lt = [\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FC
|
||||
Lu = [\u0041-\u005A\u00C0-\u00D6\u00D8-\u00DE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uFF21-\uFF3A]
|
||||
|
||||
// Number, Letter
|
||||
Nl = [\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF]
|
||||
Nl = [\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF]
|
||||
|
||||
@@ -107,6 +107,9 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
||||
if (alignment === 'rand') {
|
||||
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
|
||||
}
|
||||
if (alignment === 'feet') {
|
||||
return strudel.fastcat(...children);
|
||||
}
|
||||
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
||||
if (weightedChildren) {
|
||||
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mini",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -73,6 +73,10 @@ describe('mini', () => {
|
||||
expect(minS('a!3 b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']);
|
||||
expect(minS('[<a b c>]!3 d')).toEqual(minS('<a b c> <a b c> <a b c> d'));
|
||||
});
|
||||
it('supports replication via repeated !', () => {
|
||||
expect(minS('a ! ! b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']);
|
||||
expect(minS('[<a b c>]!! d')).toEqual(minS('<a b c> <a b c> <a b c> d'));
|
||||
});
|
||||
it('supports euclidean rhythms', () => {
|
||||
expect(minS('a(3, 8)')).toEqual(['a: 0 - 1/8', 'a: 3/8 - 1/2', 'a: 3/4 - 7/8']);
|
||||
});
|
||||
@@ -190,6 +194,16 @@ describe('mini', () => {
|
||||
it('supports patterned ranges', () => {
|
||||
expect(minS('[<0 1> .. <2 4>]*2')).toEqual(minS('[0 1 2] [1 2 3 4]'));
|
||||
});
|
||||
it('supports the . operator', () => {
|
||||
expect(minS('a . b c')).toEqual(minS('a [b c]'));
|
||||
expect(minS('a . b c . [d e f . g h]')).toEqual(minS('a [b c] [[d e f] [g h]]'));
|
||||
});
|
||||
it('supports the _ operator', () => {
|
||||
expect(minS('a _ b _ _')).toEqual(minS('a@2 b@3'));
|
||||
});
|
||||
it('_ and @ are almost interchangeable', () => {
|
||||
expect(minS('a @ b @ @')).toEqual(minS('a _2 b _3'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLeafLocation', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/osc",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "OSC messaging for strudel",
|
||||
"main": "osc.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/repl",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Strudel REPL as a Web Component",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/serial",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Webserial API for strudel",
|
||||
"main": "serial.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/soundfonts",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Soundsfont support for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superdough",
|
||||
"version": "0.10.0",
|
||||
"version": "1.0.0",
|
||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/tonal",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Tonal functions for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -157,9 +157,9 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
|
||||
* .scale("C:<major minor>/2")
|
||||
* .s("piano")
|
||||
* @example
|
||||
* n(rand.range(0,12).segment(8).round())
|
||||
* n(rand.range(0,12).segment(8))
|
||||
* .scale("C:ritusen")
|
||||
* .s("folkharp")
|
||||
* .s("piano")
|
||||
*/
|
||||
|
||||
export const scale = register('scale', function (scale, pat) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/transpiler",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/web",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
|
||||
"main": "web.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/webaudio",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Web Audio helpers for Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/xen",
|
||||
"version": "0.11.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Xenharmonic API for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -476,20 +476,29 @@ importers:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@vitest/ui@1.1.0)
|
||||
|
||||
tools/dbpatch:
|
||||
dependencies:
|
||||
csv:
|
||||
specifier: ^6.3.6
|
||||
version: 6.3.6
|
||||
|
||||
website:
|
||||
dependencies:
|
||||
'@algolia/client-search':
|
||||
specifier: ^4.22.0
|
||||
version: 4.22.0
|
||||
'@astro-community/astro-embed-youtube':
|
||||
specifier: ^0.4.3
|
||||
version: 0.4.3(astro@4.0.8)
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4(astro@4.0.8)
|
||||
'@astrojs/mdx':
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3(astro@4.0.8)
|
||||
'@astrojs/react':
|
||||
specifier: ^3.0.9
|
||||
version: 3.0.9(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)(vite@5.0.11)
|
||||
'@astrojs/rss':
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0(astro@4.0.8)(tailwindcss@3.4.0)
|
||||
@@ -803,8 +812,8 @@ packages:
|
||||
leven: 3.1.0
|
||||
dev: true
|
||||
|
||||
/@astro-community/astro-embed-youtube@0.4.3(astro@4.0.8):
|
||||
resolution: {integrity: sha512-zXtPmR9yxrTo6cuLhH8v+r62bsXbsLJgsU2FiZalPr4bXJxAUQEIlG46S/qK0AEXi9uNShKqy4+zBaJ98xTVEg==}
|
||||
/@astro-community/astro-embed-youtube@0.4.4(astro@4.0.8):
|
||||
resolution: {integrity: sha512-fYlycLrJFNnibZ9VHPSJO766kO2IgqYQU4mBd4iaDMaicL0gGX9cVZ80QdnpzGrI6w0XOJOY7prx86eWEVBy8w==}
|
||||
peerDependencies:
|
||||
astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta
|
||||
dependencies:
|
||||
@@ -890,6 +899,13 @@ packages:
|
||||
- vite
|
||||
dev: false
|
||||
|
||||
/@astrojs/rss@4.0.2:
|
||||
resolution: {integrity: sha512-Hb9GKAyvsn5EUjZtB6SniesBScMQe7SQinEHLY5EFa74QEvgcWaXTmA0Mb0P3vqDSN3d/NTYbGivprrSAawfnA==}
|
||||
dependencies:
|
||||
fast-xml-parser: 4.3.3
|
||||
kleur: 4.1.5
|
||||
dev: false
|
||||
|
||||
/@astrojs/tailwind@5.1.0(astro@4.0.8)(tailwindcss@3.4.0):
|
||||
resolution: {integrity: sha512-BJoCDKuWhU9FT2qYg+fr6Nfb3qP4ShtyjXGHKA/4mHN94z7BGcmauQK23iy+YH5qWvTnhqkd6mQPQ1yTZTe9Ig==}
|
||||
peerDependencies:
|
||||
@@ -6213,6 +6229,28 @@ packages:
|
||||
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
|
||||
dev: false
|
||||
|
||||
/csv-generate@4.3.1:
|
||||
resolution: {integrity: sha512-7YeeJq+44/I/O5N2sr2qBMcHZXhpfe38eh7DOFxyMtYO+Pir7kIfgFkW5MPksqKqqR6+/wX7UGoZm1Ot11151w==}
|
||||
dev: false
|
||||
|
||||
/csv-parse@5.5.3:
|
||||
resolution: {integrity: sha512-v0KW6C0qlZzoGjk6u5tLmVfyZxNgPGXZsWTXshpAgKVGmGXzaVWGdlCFxNx5iuzcXT/oJN1HHM9DZKwtAtYa+A==}
|
||||
dev: false
|
||||
|
||||
/csv-stringify@6.4.5:
|
||||
resolution: {integrity: sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ==}
|
||||
dev: false
|
||||
|
||||
/csv@6.3.6:
|
||||
resolution: {integrity: sha512-jsEsX2HhGp7xiwrJu5srQavKsh+HUJcCi78Ar3m4jlmFKRoTkkMy7ZZPP+LnQChmaztW+uj44oyfMb59daAs/Q==}
|
||||
engines: {node: '>= 0.1.90'}
|
||||
dependencies:
|
||||
csv-generate: 4.3.1
|
||||
csv-parse: 5.5.3
|
||||
csv-stringify: 6.4.5
|
||||
stream-transform: 3.3.0
|
||||
dev: false
|
||||
|
||||
/dargs@7.0.0:
|
||||
resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7182,6 +7220,13 @@ packages:
|
||||
/fast-levenshtein@2.0.6:
|
||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||
|
||||
/fast-xml-parser@4.3.3:
|
||||
resolution: {integrity: sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
strnum: 1.0.5
|
||||
dev: false
|
||||
|
||||
/fastq@1.15.0:
|
||||
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
|
||||
dependencies:
|
||||
@@ -12457,6 +12502,10 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/stream-transform@3.3.0:
|
||||
resolution: {integrity: sha512-pG1NeDdmErNYKtvTpFayrEueAmL0xVU5wd22V7InGnatl4Ocq3HY7dcXIKj629kXvYQvglCC7CeDIGAlx1RNGA==}
|
||||
dev: false
|
||||
|
||||
/stream-via@1.0.4:
|
||||
resolution: {integrity: sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -12637,6 +12686,10 @@ packages:
|
||||
acorn: 8.11.3
|
||||
dev: true
|
||||
|
||||
/strnum@1.0.5:
|
||||
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
|
||||
dev: false
|
||||
|
||||
/strong-log-transformer@2.1.0:
|
||||
resolution: {integrity: sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
@@ -2,4 +2,5 @@ packages:
|
||||
# all packages in direct subdirs of packages/
|
||||
- "packages/*"
|
||||
- "examples/*"
|
||||
- "tools/dbpatch"
|
||||
- "website/"
|
||||
|
||||
@@ -339,402 +339,6 @@ exports[`renders tunes > tune: blippyRhodes 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders tunes > tune: bridgeIsOver 1`] = `
|
||||
[
|
||||
"[ -155/52 ⇜ (0/1 → 5/52) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ -75/26 ⇜ (0/1 → 5/26) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ -145/52 ⇜ (0/1 → 15/52) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ -35/13 ⇜ (0/1 → 5/13) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ -35/13 ⇜ (0/1 → 5/13) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ -135/52 ⇜ (0/1 → 5/13) ⇝ 25/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ -5/2 ⇜ (0/1 → 5/13) ⇝ 15/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ -125/52 ⇜ (0/1 → 5/13) ⇝ 35/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ -30/13 ⇜ (0/1 → 5/13) ⇝ 10/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ -115/52 ⇜ (0/1 → 5/13) ⇝ 45/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ -55/26 ⇜ (0/1 → 5/13) ⇝ 25/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ -105/52 ⇜ (0/1 → 5/13) ⇝ 55/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 0/1 → 5/13 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ -135/52 ⇜ (0/1 → 25/52) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ -5/2 ⇜ (0/1 → 15/26) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ -125/52 ⇜ (0/1 → 35/52) | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ -30/13 ⇜ (0/1 → 10/13) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ -115/52 ⇜ (0/1 → 10/13) ⇝ 45/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ -55/26 ⇜ (0/1 → 10/13) ⇝ 25/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ -105/52 ⇜ (0/1 → 10/13) ⇝ 55/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ -25/13 ⇜ (0/1 → 10/13) ⇝ 15/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ -95/52 ⇜ (0/1 → 10/13) ⇝ 5/4 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ -45/26 ⇜ (0/1 → 10/13) ⇝ 35/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ -85/52 ⇜ (0/1 → 10/13) ⇝ 75/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 40/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 80/13 | s:mad ]",
|
||||
"[ (5/52 → 1/1) ⇝ 165/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ (5/26 → 1/1) ⇝ 85/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (15/52 → 1/1) ⇝ 175/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ -135/52 ⇜ (5/13 → 25/52) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ -5/2 ⇜ (5/13 → 15/26) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ -125/52 ⇜ (5/13 → 35/52) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ -30/13 ⇜ (5/13 → 10/13) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 5/13 → 10/13 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ -115/52 ⇜ (5/13 → 45/52) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ -55/26 ⇜ (5/13 → 25/26) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ -105/52 ⇜ (5/13 → 1/1) ⇝ 55/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (5/13 → 1/1) ⇝ 45/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ (5/13 → 1/1) ⇝ 45/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ (25/52 → 1/1) ⇝ 185/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ (25/52 → 1/1) ⇝ 185/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ (15/26 → 1/1) ⇝ 95/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (15/26 → 1/1) ⇝ 95/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (35/52 → 1/1) ⇝ 15/4 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (35/52 → 1/1) ⇝ 15/4 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ -115/52 ⇜ (10/13 → 45/52) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ -55/26 ⇜ (10/13 → 25/26) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 10/13 → 155/156 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ -105/52 ⇜ (10/13 → 1/1) ⇝ 55/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ -25/13 ⇜ (10/13 → 1/1) ⇝ 15/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ -95/52 ⇜ (10/13 → 1/1) ⇝ 5/4 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ -45/26 ⇜ (10/13 → 1/1) ⇝ 35/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ -85/52 ⇜ (10/13 → 1/1) ⇝ 75/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (10/13 → 1/1) ⇝ 50/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ (10/13 → 1/1) ⇝ 50/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ (45/52 → 1/1) ⇝ 205/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ (45/52 → 1/1) ⇝ 205/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ (25/26 → 1/1) ⇝ 105/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (25/26 → 1/1) ⇝ 105/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (155/156 → 1/1) ⇝ 15/13 | note:bb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.46296296296296297 ]",
|
||||
"[ -105/52 ⇜ (1/1 → 55/52) | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ -105/52 ⇜ (1/1 → 55/52) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ -25/13 ⇜ (1/1 → 15/13) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 155/156 ⇜ (1/1 → 15/13) | note:bb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.46296296296296297 ]",
|
||||
"[ -95/52 ⇜ (1/1 → 5/4) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ -45/26 ⇜ (1/1 → 35/26) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ -85/52 ⇜ (1/1 → 75/52) | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 0/1 ⇜ (1/1 → 2/1) ⇝ 40/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 0/1 ⇜ (1/1 → 2/1) ⇝ 80/13 | s:mad ]",
|
||||
"[ 5/52 ⇜ (1/1 → 2/1) ⇝ 165/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 5/26 ⇜ (1/1 → 2/1) ⇝ 85/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 15/52 ⇜ (1/1 → 2/1) ⇝ 175/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 5/13 ⇜ (1/1 → 2/1) ⇝ 45/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/13 ⇜ (1/1 → 2/1) ⇝ 45/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 25/52 ⇜ (1/1 → 2/1) ⇝ 185/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 25/52 ⇜ (1/1 → 2/1) ⇝ 185/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 15/26 ⇜ (1/1 → 2/1) ⇝ 95/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 15/26 ⇜ (1/1 → 2/1) ⇝ 95/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 35/52 ⇜ (1/1 → 2/1) ⇝ 15/4 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 35/52 ⇜ (1/1 → 2/1) ⇝ 15/4 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 10/13 ⇜ (1/1 → 2/1) ⇝ 50/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 10/13 ⇜ (1/1 → 2/1) ⇝ 50/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 45/52 ⇜ (1/1 → 2/1) ⇝ 205/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 45/52 ⇜ (1/1 → 2/1) ⇝ 205/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 25/26 ⇜ (1/1 → 2/1) ⇝ 105/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 25/26 ⇜ (1/1 → 2/1) ⇝ 105/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (55/52 → 2/1) ⇝ 215/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (55/52 → 2/1) ⇝ 215/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/13 → 20/13 | note:ab2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4537037037037037 ]",
|
||||
"[ (15/13 → 2/1) ⇝ 55/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ (5/4 → 2/1) ⇝ 225/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ (35/26 → 2/1) ⇝ 115/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (75/52 → 2/1) ⇝ 235/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 20/13 → 25/13 | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ (25/13 → 2/1) ⇝ 30/13 | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ 25/13 ⇜ (2/1 → 30/13) | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ 0/1 ⇜ (2/1 → 3/1) ⇝ 40/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 0/1 ⇜ (2/1 → 3/1) ⇝ 80/13 | s:mad ]",
|
||||
"[ 5/52 ⇜ (2/1 → 3/1) ⇝ 165/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 5/26 ⇜ (2/1 → 3/1) ⇝ 85/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 15/52 ⇜ (2/1 → 3/1) ⇝ 175/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 5/13 ⇜ (2/1 → 3/1) ⇝ 45/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/13 ⇜ (2/1 → 3/1) ⇝ 45/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 25/52 ⇜ (2/1 → 3/1) ⇝ 185/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 25/52 ⇜ (2/1 → 3/1) ⇝ 185/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 15/26 ⇜ (2/1 → 3/1) ⇝ 95/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 15/26 ⇜ (2/1 → 3/1) ⇝ 95/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 35/52 ⇜ (2/1 → 3/1) ⇝ 15/4 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 35/52 ⇜ (2/1 → 3/1) ⇝ 15/4 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 10/13 ⇜ (2/1 → 3/1) ⇝ 50/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 10/13 ⇜ (2/1 → 3/1) ⇝ 50/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 45/52 ⇜ (2/1 → 3/1) ⇝ 205/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 45/52 ⇜ (2/1 → 3/1) ⇝ 205/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 25/26 ⇜ (2/1 → 3/1) ⇝ 105/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 25/26 ⇜ (2/1 → 3/1) ⇝ 105/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 55/52 ⇜ (2/1 → 3/1) ⇝ 215/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 55/52 ⇜ (2/1 → 3/1) ⇝ 215/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/13 ⇜ (2/1 → 3/1) ⇝ 55/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/4 ⇜ (2/1 → 3/1) ⇝ 225/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 35/26 ⇜ (2/1 → 3/1) ⇝ 115/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 75/52 ⇜ (2/1 → 3/1) ⇝ 235/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 30/13 → 395/156 | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ 395/156 → 35/13 | note:ab2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4537037037037037 ]",
|
||||
"[ (35/13 → 3/1) ⇝ 40/13 | note:bb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.46296296296296297 ]",
|
||||
"[ 0/1 ⇜ (3/1 → 40/13) | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 0/1 ⇜ (3/1 → 40/13) ⇝ 80/13 | s:mad ]",
|
||||
"[ 5/52 ⇜ (3/1 → 40/13) ⇝ 165/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 5/26 ⇜ (3/1 → 40/13) ⇝ 85/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 15/52 ⇜ (3/1 → 40/13) ⇝ 175/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 5/13 ⇜ (3/1 → 40/13) ⇝ 45/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/13 ⇜ (3/1 → 40/13) ⇝ 45/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 25/52 ⇜ (3/1 → 40/13) ⇝ 185/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 25/52 ⇜ (3/1 → 40/13) ⇝ 185/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 15/26 ⇜ (3/1 → 40/13) ⇝ 95/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 15/26 ⇜ (3/1 → 40/13) ⇝ 95/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 35/52 ⇜ (3/1 → 40/13) ⇝ 15/4 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 35/52 ⇜ (3/1 → 40/13) ⇝ 15/4 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 10/13 ⇜ (3/1 → 40/13) ⇝ 50/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 10/13 ⇜ (3/1 → 40/13) ⇝ 50/13 | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 45/52 ⇜ (3/1 → 40/13) ⇝ 205/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 45/52 ⇜ (3/1 → 40/13) ⇝ 205/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 25/26 ⇜ (3/1 → 40/13) ⇝ 105/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 25/26 ⇜ (3/1 → 40/13) ⇝ 105/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 55/52 ⇜ (3/1 → 40/13) ⇝ 215/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 55/52 ⇜ (3/1 → 40/13) ⇝ 215/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/13 ⇜ (3/1 → 40/13) ⇝ 55/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/4 ⇜ (3/1 → 40/13) ⇝ 225/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 35/26 ⇜ (3/1 → 40/13) ⇝ 115/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 75/52 ⇜ (3/1 → 40/13) ⇝ 235/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 35/13 ⇜ (3/1 → 40/13) | note:bb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.46296296296296297 ]",
|
||||
"[ 5/52 ⇜ (40/13 → 165/52) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 5/26 ⇜ (40/13 → 85/26) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 15/52 ⇜ (40/13 → 175/52) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 5/13 ⇜ (40/13 → 45/13) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/13 ⇜ (40/13 → 45/13) | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 25/52 ⇜ (40/13 → 45/13) ⇝ 185/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 15/26 ⇜ (40/13 → 45/13) ⇝ 95/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 35/52 ⇜ (40/13 → 45/13) ⇝ 15/4 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 10/13 ⇜ (40/13 → 45/13) ⇝ 50/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 45/52 ⇜ (40/13 → 45/13) ⇝ 205/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 25/26 ⇜ (40/13 → 45/13) ⇝ 105/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 55/52 ⇜ (40/13 → 45/13) ⇝ 215/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 40/13 → 45/13 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ 25/52 ⇜ (40/13 → 185/52) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 15/26 ⇜ (40/13 → 95/26) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 35/52 ⇜ (40/13 → 15/4) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 10/13 ⇜ (40/13 → 50/13) | clip:1 note:C4 s:piano release:0.1 pan:0.5277777777777778 ]",
|
||||
"[ 45/52 ⇜ (40/13 → 50/13) ⇝ 205/52 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 25/26 ⇜ (40/13 → 50/13) ⇝ 105/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 55/52 ⇜ (40/13 → 50/13) ⇝ 215/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/13 ⇜ (40/13 → 50/13) ⇝ 55/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/4 ⇜ (40/13 → 50/13) ⇝ 225/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 35/26 ⇜ (40/13 → 50/13) ⇝ 115/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 75/52 ⇜ (40/13 → 50/13) ⇝ 235/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 0/1 ⇜ (40/13 → 4/1) ⇝ 80/13 | s:mad ]",
|
||||
"[ (40/13 → 4/1) ⇝ 80/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ (165/52 → 4/1) ⇝ 25/4 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (85/26 → 4/1) ⇝ 165/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ (175/52 → 4/1) ⇝ 335/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 25/52 ⇜ (45/13 → 185/52) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 15/26 ⇜ (45/13 → 95/26) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 35/52 ⇜ (45/13 → 15/4) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 10/13 ⇜ (45/13 → 50/13) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 45/13 → 50/13 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ 45/52 ⇜ (45/13 → 205/52) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 25/26 ⇜ (45/13 → 4/1) ⇝ 105/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 55/52 ⇜ (45/13 → 4/1) ⇝ 215/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (45/13 → 4/1) ⇝ 85/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ (45/13 → 4/1) ⇝ 85/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ (185/52 → 4/1) ⇝ 345/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (185/52 → 4/1) ⇝ 345/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (95/26 → 4/1) ⇝ 175/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (95/26 → 4/1) ⇝ 175/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ (15/4 → 4/1) ⇝ 355/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (15/4 → 4/1) ⇝ 355/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 45/52 ⇜ (50/13 → 205/52) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 25/26 ⇜ (50/13 → 4/1) ⇝ 105/26 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 55/52 ⇜ (50/13 → 4/1) ⇝ 215/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/13 ⇜ (50/13 → 4/1) ⇝ 55/13 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/4 ⇜ (50/13 → 4/1) ⇝ 225/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 35/26 ⇜ (50/13 → 4/1) ⇝ 115/26 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 75/52 ⇜ (50/13 → 4/1) ⇝ 235/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (50/13 → 4/1) ⇝ 635/156 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ (50/13 → 4/1) ⇝ 90/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ (50/13 → 4/1) ⇝ 90/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ (205/52 → 4/1) ⇝ 365/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (205/52 → 4/1) ⇝ 365/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 25/26 ⇜ (4/1 → 105/26) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 25/26 ⇜ (4/1 → 105/26) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 50/13 ⇜ (4/1 → 635/156) | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ 55/52 ⇜ (4/1 → 215/52) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 55/52 ⇜ (4/1 → 215/52) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/13 ⇜ (4/1 → 55/13) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 5/4 ⇜ (4/1 → 225/52) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 35/26 ⇜ (4/1 → 115/26) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 75/52 ⇜ (4/1 → 235/52) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 0/1 ⇜ (4/1 → 5/1) ⇝ 80/13 | s:mad ]",
|
||||
"[ 40/13 ⇜ (4/1 → 5/1) ⇝ 80/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 165/52 ⇜ (4/1 → 5/1) ⇝ 25/4 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 85/26 ⇜ (4/1 → 5/1) ⇝ 165/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 175/52 ⇜ (4/1 → 5/1) ⇝ 335/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 45/13 ⇜ (4/1 → 5/1) ⇝ 85/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 45/13 ⇜ (4/1 → 5/1) ⇝ 85/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 185/52 ⇜ (4/1 → 5/1) ⇝ 345/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 185/52 ⇜ (4/1 → 5/1) ⇝ 345/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 95/26 ⇜ (4/1 → 5/1) ⇝ 175/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 95/26 ⇜ (4/1 → 5/1) ⇝ 175/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/4 ⇜ (4/1 → 5/1) ⇝ 355/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 15/4 ⇜ (4/1 → 5/1) ⇝ 355/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 50/13 ⇜ (4/1 → 5/1) ⇝ 90/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 50/13 ⇜ (4/1 → 5/1) ⇝ 90/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 205/52 ⇜ (4/1 → 5/1) ⇝ 365/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 205/52 ⇜ (4/1 → 5/1) ⇝ 365/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (105/26 → 5/1) ⇝ 185/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (105/26 → 5/1) ⇝ 185/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 635/156 → 55/13 | note:bb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.46296296296296297 ]",
|
||||
"[ (215/52 → 5/1) ⇝ 375/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (215/52 → 5/1) ⇝ 375/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 55/13 → 60/13 | note:ab2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4537037037037037 ]",
|
||||
"[ (55/13 → 5/1) ⇝ 95/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ (225/52 → 5/1) ⇝ 385/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (115/26 → 5/1) ⇝ 15/2 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (235/52 → 5/1) ⇝ 395/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 60/13 → 5/1 | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ 5/1 → 70/13 | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ 0/1 ⇜ (5/1 → 6/1) ⇝ 80/13 | s:mad ]",
|
||||
"[ 40/13 ⇜ (5/1 → 6/1) ⇝ 80/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 165/52 ⇜ (5/1 → 6/1) ⇝ 25/4 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 85/26 ⇜ (5/1 → 6/1) ⇝ 165/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 175/52 ⇜ (5/1 → 6/1) ⇝ 335/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 45/13 ⇜ (5/1 → 6/1) ⇝ 85/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 45/13 ⇜ (5/1 → 6/1) ⇝ 85/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 185/52 ⇜ (5/1 → 6/1) ⇝ 345/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 185/52 ⇜ (5/1 → 6/1) ⇝ 345/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 95/26 ⇜ (5/1 → 6/1) ⇝ 175/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 95/26 ⇜ (5/1 → 6/1) ⇝ 175/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/4 ⇜ (5/1 → 6/1) ⇝ 355/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 15/4 ⇜ (5/1 → 6/1) ⇝ 355/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 50/13 ⇜ (5/1 → 6/1) ⇝ 90/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 50/13 ⇜ (5/1 → 6/1) ⇝ 90/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 205/52 ⇜ (5/1 → 6/1) ⇝ 365/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 205/52 ⇜ (5/1 → 6/1) ⇝ 365/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 105/26 ⇜ (5/1 → 6/1) ⇝ 185/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 105/26 ⇜ (5/1 → 6/1) ⇝ 185/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 215/52 ⇜ (5/1 → 6/1) ⇝ 375/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 215/52 ⇜ (5/1 → 6/1) ⇝ 375/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 55/13 ⇜ (5/1 → 6/1) ⇝ 95/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 225/52 ⇜ (5/1 → 6/1) ⇝ 385/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 115/26 ⇜ (5/1 → 6/1) ⇝ 15/2 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 235/52 ⇜ (5/1 → 6/1) ⇝ 395/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (70/13 → 6/1) ⇝ 80/13 | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ 0/1 ⇜ (6/1 → 80/13) | s:mad ]",
|
||||
"[ 40/13 ⇜ (6/1 → 80/13) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 165/52 ⇜ (6/1 → 80/13) ⇝ 25/4 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 85/26 ⇜ (6/1 → 80/13) ⇝ 165/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 175/52 ⇜ (6/1 → 80/13) ⇝ 335/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 45/13 ⇜ (6/1 → 80/13) ⇝ 85/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 45/13 ⇜ (6/1 → 80/13) ⇝ 85/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 185/52 ⇜ (6/1 → 80/13) ⇝ 345/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 185/52 ⇜ (6/1 → 80/13) ⇝ 345/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 95/26 ⇜ (6/1 → 80/13) ⇝ 175/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 95/26 ⇜ (6/1 → 80/13) ⇝ 175/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/4 ⇜ (6/1 → 80/13) ⇝ 355/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 15/4 ⇜ (6/1 → 80/13) ⇝ 355/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 50/13 ⇜ (6/1 → 80/13) ⇝ 90/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 50/13 ⇜ (6/1 → 80/13) ⇝ 90/13 | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 205/52 ⇜ (6/1 → 80/13) ⇝ 365/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 205/52 ⇜ (6/1 → 80/13) ⇝ 365/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 105/26 ⇜ (6/1 → 80/13) ⇝ 185/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 105/26 ⇜ (6/1 → 80/13) ⇝ 185/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 215/52 ⇜ (6/1 → 80/13) ⇝ 375/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 215/52 ⇜ (6/1 → 80/13) ⇝ 375/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 55/13 ⇜ (6/1 → 80/13) ⇝ 95/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 225/52 ⇜ (6/1 → 80/13) ⇝ 385/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 115/26 ⇜ (6/1 → 80/13) ⇝ 15/2 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 235/52 ⇜ (6/1 → 80/13) ⇝ 395/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 70/13 ⇜ (6/1 → 80/13) | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
"[ 165/52 ⇜ (80/13 → 25/4) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 85/26 ⇜ (80/13 → 165/26) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 175/52 ⇜ (80/13 → 335/52) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 45/13 ⇜ (80/13 → 85/13) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 45/13 ⇜ (80/13 → 85/13) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 185/52 ⇜ (80/13 → 85/13) ⇝ 345/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 95/26 ⇜ (80/13 → 85/13) ⇝ 175/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/4 ⇜ (80/13 → 85/13) ⇝ 355/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 50/13 ⇜ (80/13 → 85/13) ⇝ 90/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 205/52 ⇜ (80/13 → 85/13) ⇝ 365/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 105/26 ⇜ (80/13 → 85/13) ⇝ 185/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 215/52 ⇜ (80/13 → 85/13) ⇝ 375/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 80/13 → 85/13 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ 185/52 ⇜ (80/13 → 345/52) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 95/26 ⇜ (80/13 → 175/26) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 15/4 ⇜ (80/13 → 355/52) | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 50/13 ⇜ (80/13 → 90/13) | clip:1 note:D4 s:piano release:0.1 pan:0.537037037037037 ]",
|
||||
"[ 205/52 ⇜ (80/13 → 90/13) ⇝ 365/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 105/26 ⇜ (80/13 → 90/13) ⇝ 185/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 215/52 ⇜ (80/13 → 90/13) ⇝ 375/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 55/13 ⇜ (80/13 → 90/13) ⇝ 95/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 225/52 ⇜ (80/13 → 90/13) ⇝ 385/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 115/26 ⇜ (80/13 → 90/13) ⇝ 15/2 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 235/52 ⇜ (80/13 → 90/13) ⇝ 395/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (80/13 → 7/1) ⇝ 120/13 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (80/13 → 7/1) ⇝ 160/13 | s:mad ]",
|
||||
"[ (25/4 → 7/1) ⇝ 485/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ (165/26 → 7/1) ⇝ 245/26 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ (335/52 → 7/1) ⇝ 495/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 185/52 ⇜ (85/13 → 345/52) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 95/26 ⇜ (85/13 → 175/26) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 15/4 ⇜ (85/13 → 355/52) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 50/13 ⇜ (85/13 → 90/13) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 85/13 → 90/13 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ 205/52 ⇜ (85/13 → 7/1) ⇝ 365/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 105/26 ⇜ (85/13 → 7/1) ⇝ 185/26 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 215/52 ⇜ (85/13 → 7/1) ⇝ 375/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (85/13 → 7/1) ⇝ 125/13 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (85/13 → 7/1) ⇝ 125/13 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (345/52 → 7/1) ⇝ 505/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (345/52 → 7/1) ⇝ 505/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ (175/26 → 7/1) ⇝ 255/26 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (175/26 → 7/1) ⇝ 255/26 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ (355/52 → 7/1) ⇝ 515/52 | clip:1 note:F#5 s:piano release:0.1 pan:0.6111111111111112 ]",
|
||||
"[ (355/52 → 7/1) ⇝ 515/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 205/52 ⇜ (90/13 → 7/1) ⇝ 365/52 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 105/26 ⇜ (90/13 → 7/1) ⇝ 185/26 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 215/52 ⇜ (90/13 → 7/1) ⇝ 375/52 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 55/13 ⇜ (90/13 → 7/1) ⇝ 95/13 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 225/52 ⇜ (90/13 → 7/1) ⇝ 385/52 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 115/26 ⇜ (90/13 → 7/1) ⇝ 15/2 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 235/52 ⇜ (90/13 → 7/1) ⇝ 395/52 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (90/13 → 7/1) ⇝ 1115/156 | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ (90/13 → 7/1) ⇝ 10/1 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (90/13 → 7/1) ⇝ 10/1 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 205/52 ⇜ (7/1 → 365/52) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 205/52 ⇜ (7/1 → 365/52) | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 105/26 ⇜ (7/1 → 185/26) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 105/26 ⇜ (7/1 → 185/26) | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 90/13 ⇜ (7/1 → 1115/156) | note:c3 gain:0.8 clip:1 s:piano release:0.1 pan:0.4722222222222222 ]",
|
||||
"[ 215/52 ⇜ (7/1 → 375/52) | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 215/52 ⇜ (7/1 → 375/52) | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 55/13 ⇜ (7/1 → 95/13) | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 225/52 ⇜ (7/1 → 385/52) | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 115/26 ⇜ (7/1 → 15/2) | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 235/52 ⇜ (7/1 → 395/52) | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 80/13 ⇜ (7/1 → 8/1) ⇝ 120/13 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 80/13 ⇜ (7/1 → 8/1) ⇝ 160/13 | s:mad ]",
|
||||
"[ 25/4 ⇜ (7/1 → 8/1) ⇝ 485/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 165/26 ⇜ (7/1 → 8/1) ⇝ 245/26 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 335/52 ⇜ (7/1 → 8/1) ⇝ 495/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 85/13 ⇜ (7/1 → 8/1) ⇝ 125/13 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 85/13 ⇜ (7/1 → 8/1) ⇝ 125/13 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ 345/52 ⇜ (7/1 → 8/1) ⇝ 505/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ 345/52 ⇜ (7/1 → 8/1) ⇝ 505/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ 175/26 ⇜ (7/1 → 8/1) ⇝ 255/26 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ 175/26 ⇜ (7/1 → 8/1) ⇝ 255/26 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 355/52 ⇜ (7/1 → 8/1) ⇝ 515/52 | clip:1 note:F#5 s:piano release:0.1 pan:0.6111111111111112 ]",
|
||||
"[ 355/52 ⇜ (7/1 → 8/1) ⇝ 515/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 90/13 ⇜ (7/1 → 8/1) ⇝ 10/1 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ 90/13 ⇜ (7/1 → 8/1) ⇝ 10/1 | clip:1 note:E4 s:piano release:0.1 pan:0.5462962962962963 ]",
|
||||
"[ (365/52 → 8/1) ⇝ 525/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (365/52 → 8/1) ⇝ 525/52 | clip:1 note:F#4 s:piano release:0.1 pan:0.5555555555555556 ]",
|
||||
"[ (185/26 → 8/1) ⇝ 265/26 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (185/26 → 8/1) ⇝ 265/26 | clip:1 note:G#4 s:piano release:0.1 pan:0.5648148148148149 ]",
|
||||
"[ 1115/156 → 95/13 | note:bb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.46296296296296297 ]",
|
||||
"[ (375/52 → 8/1) ⇝ 535/52 | clip:1 note:F#5 s:piano release:0.1 pan:0.6111111111111112 ]",
|
||||
"[ (375/52 → 8/1) ⇝ 535/52 | clip:1 note:A#4 s:piano release:0.1 pan:0.5740740740740741 ]",
|
||||
"[ 95/13 → 100/13 | note:ab2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4537037037037037 ]",
|
||||
"[ (95/13 → 8/1) ⇝ 135/13 | clip:1 note:C5 s:piano release:0.1 pan:0.5833333333333333 ]",
|
||||
"[ (385/52 → 8/1) ⇝ 545/52 | clip:1 note:D5 s:piano release:0.1 pan:0.5925925925925926 ]",
|
||||
"[ (15/2 → 8/1) ⇝ 275/26 | clip:1 note:E5 s:piano release:0.1 pan:0.6018518518518519 ]",
|
||||
"[ (395/52 → 8/1) ⇝ 555/52 | clip:1 note:F#5 s:piano release:0.1 pan:0.6111111111111112 ]",
|
||||
"[ (100/13 → 8/1) ⇝ 105/13 | note:gb2 gain:0.8 clip:1 s:piano release:0.1 pan:0.4444444444444444 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders tunes > tune: caverave 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd gain:0.8 ]",
|
||||
@@ -1507,14 +1111,14 @@ exports[`renders tunes > tune: caverave 1`] = `
|
||||
|
||||
exports[`renders tunes > tune: chop 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:p speed:0.03125 unit:c begin:0 end:0.0078125 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 0/1 → 1/4 | s:p speed:0.03125 unit:c begin:0.0234375 end:0.03125 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/4 → 1/2 | s:p speed:0.03125 unit:c begin:0.0078125 end:0.015625 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/4 → 1/2 | s:p speed:0.03125 unit:c begin:0.015625 end:0.0234375 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/2 → 3/4 | s:p speed:0.03125 unit:c begin:0.015625 end:0.0234375 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/2 → 3/4 | s:p speed:0.03125 unit:c begin:0.0078125 end:0.015625 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 3/4 → 1/1 | s:p speed:0.03125 unit:c begin:0.0234375 end:0.03125 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 3/4 → 1/1 | s:p speed:0.03125 unit:c begin:0 end:0.0078125 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 0/1 → 1/4 | s:p speed:0.015625 unit:c begin:0 end:0.0078125 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 0/1 → 1/4 | s:p speed:0.015625 unit:c begin:0.0234375 end:0.03125 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/4 → 1/2 | s:p speed:0.015625 unit:c begin:0.0078125 end:0.015625 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/4 → 1/2 | s:p speed:0.015625 unit:c begin:0.015625 end:0.0234375 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/2 → 3/4 | s:p speed:0.015625 unit:c begin:0.015625 end:0.0234375 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 1/2 → 3/4 | s:p speed:0.015625 unit:c begin:0.0078125 end:0.015625 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 3/4 → 1/1 | s:p speed:0.015625 unit:c begin:0.0234375 end:0.03125 pan:0 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
"[ 3/4 → 1/1 | s:p speed:0.015625 unit:c begin:0 end:0.0078125 pan:1 shape:0.4 decay:0.1 sustain:0.6 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -1545,7 +1149,7 @@ exports[`renders tunes > tune: dinofunk 1`] = `
|
||||
"[ 0/1 → 1/4 | note:Ab4 s:sawtooth cutoff:1239.2541394619345 gain:0.8 decay:0.05125097280354112 sustain:0 delay:0.2561353071307281 room:1 ]",
|
||||
"[ 0/1 → 1/4 | note:68.1 s:sawtooth cutoff:1239.2541394619345 gain:0.8 decay:0.05125097280354112 sustain:0 delay:0.2561353071307281 room:1 ]",
|
||||
"[ 0/1 → 1/2 | s:bd ]",
|
||||
"[ (0/1 → 1/1) ⇝ 8/1 | s:bass speed:0.125 unit:c clip:1 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 8/1 | s:bass speed:0.0625 unit:c clip:1 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 8/1 | note:b4 s:dino delay:0.8 room:0.5 ]",
|
||||
"[ 1/4 → 1/2 | s:hh ]",
|
||||
"[ 1/4 → 1/2 | note:Gb3 ]",
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const supabase = createClient(
|
||||
'https://pidxdsxphlhzjnzmifth.supabase.co',
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
|
||||
);
|
||||
|
||||
const { data } = await supabase.from('code');
|
||||
|
||||
console.log(JSON.stringify(data));
|
||||
@@ -175,6 +175,7 @@ evalScope(
|
||||
loadCSound,
|
||||
loadCsound,
|
||||
loadcsound,
|
||||
setcps: id,
|
||||
Clock: {}, // whatever
|
||||
// Tone,
|
||||
},
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { queryCode } from '../runtime.mjs';
|
||||
import { describe, it } from 'vitest';
|
||||
import data from './dbdump.json';
|
||||
|
||||
describe('renders shared tunes', async () => {
|
||||
data.forEach(({ id, code, hash }) => {
|
||||
const url = `https://strudel.cc/?${hash}`;
|
||||
it(`shared tune ${id} ${url}`, async ({ expect }) => {
|
||||
if (code.includes('import(')) {
|
||||
console.log('skip', url);
|
||||
return;
|
||||
}
|
||||
const haps = await queryCode(code, 1);
|
||||
expect(haps).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
# dbpatch
|
||||
|
||||
this is a little script to update all patterns in the db. Go to supabase and export as csv as `code_rows.csv` to this folder.
|
||||
Then run
|
||||
|
||||
```sh
|
||||
node dbpatch.mjs > code_rows_patched.csv
|
||||
```
|
||||
|
||||
It will output a csv file with the changes implemented in dbpatch.mjs
|
||||
@@ -0,0 +1,31 @@
|
||||
import { parse } from 'csv-parse/sync';
|
||||
import { readFileSync } from 'fs';
|
||||
import { stringify } from 'csv-stringify/sync';
|
||||
|
||||
const hasCpsCall = (code) =>
|
||||
['setcps', 'setCps', 'setCpm', 'setcpm'].reduce((acc, m) => acc || code.includes(`${m}`), false);
|
||||
|
||||
function withCps(code, cps) {
|
||||
if (hasCpsCall(code)) {
|
||||
return code;
|
||||
}
|
||||
const lines = code.split('\n');
|
||||
const firstNonLineComment = lines.findIndex((l) => !l.startsWith('//'));
|
||||
const cpsCall = `setcps(${cps})`;
|
||||
lines.splice(firstNonLineComment, 0, cpsCall);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
const dumpNew = readFileSync('./code_rows.csv', { encoding: 'utf-8' });
|
||||
|
||||
const records = parse(dumpNew, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
const edited = records.map((entry) => ({
|
||||
...entry,
|
||||
code: withCps(entry.code, 1),
|
||||
}));
|
||||
|
||||
console.log(stringify(edited));
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"csv": "^6.3.6"
|
||||
}
|
||||
}
|
||||
@@ -262,6 +262,13 @@ declare module 'astro:content' {
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"year-2.mdx": {
|
||||
id: "year-2.mdx";
|
||||
slug: "year-2";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^4.22.0",
|
||||
"@astro-community/astro-embed-youtube": "^0.4.3",
|
||||
"@astro-community/astro-embed-youtube": "^0.4.4",
|
||||
"@astrojs/mdx": "^2.0.3",
|
||||
"@astrojs/react": "^3.0.9",
|
||||
"@astrojs/rss": "^4.0.2",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@docsearch/css": "^3.5.2",
|
||||
"@docsearch/react": "^3.5.2",
|
||||
@@ -23,21 +24,21 @@
|
||||
"@heroicons/react": "^2.1.1",
|
||||
"@nanostores/persistent": "^0.9.1",
|
||||
"@nanostores/react": "^0.7.1",
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
"@strudel/core": "workspace:*",
|
||||
"@strudel/csound": "workspace:*",
|
||||
"@strudel/desktopbridge": "workspace:*",
|
||||
"@strudel/hydra": "workspace:*",
|
||||
"@strudel/midi": "workspace:*",
|
||||
"@strudel/mini": "workspace:*",
|
||||
"@strudel/osc": "workspace:*",
|
||||
"@strudel/repl": "workspace:*",
|
||||
"@strudel/serial": "workspace:*",
|
||||
"@strudel/soundfonts": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
"@strudel/webaudio": "workspace:*",
|
||||
"@strudel/xen": "workspace:*",
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
"@strudel/desktopbridge": "workspace:*",
|
||||
"@strudel/hydra": "workspace:*",
|
||||
"@strudel/repl": "workspace:*",
|
||||
"@supabase/supabase-js": "^2.39.1",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
|
||||
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 387 KiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 497 KiB |
|
After Width: | Height: | Size: 511 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
|
||||
type Props = CollectionEntry<'blog'>['data'];
|
||||
type Props = { post: CollectionEntry<'blog'> };
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = await post.render();
|
||||
@@ -9,11 +9,12 @@ import { format } from 'date-fns';
|
||||
---
|
||||
|
||||
<article
|
||||
class="prose max-w-none prose-headings:font-sans prose-headings:font-black prose-headings:text-slate-900 dark:prose-headings:text-gray-200 dark:text-gray-400 dark:prose-strong:text-gray-400 dark:prose-code:text-slate-400 dark:prose-a:text-gray-300 prose-a:text-slate-900 prose-blockquote:text-slate-800 dark:prose-blockquote:text-slate-400"
|
||||
class="prose max-w-none prose-headings:font-sans prose-headings:font-black prose-headings:text-slate-900 dark:prose-headings:text-gray-200 dark:text-gray-400 dark:prose-strong:text-gray-400 dark:prose-code:text-slate-400 dark:prose-a:text-gray-300 prose-a:text-slate-900 prose-blockquote:text-slate-800 dark:prose-blockquote:text-slate-400 border-b-4 border-lineHighlight pt-4"
|
||||
>
|
||||
<div class="pb-2">
|
||||
<div class="md:flex justify-between">
|
||||
<h1 class="mb-4" id={post.slug}>{post.data.title}</h1>
|
||||
<h1 class="mb-4">{post.data.title}</h1>
|
||||
<span id={post.slug} class="-mt-16"></span>
|
||||
</div>
|
||||
<p class="italic p-0 m-0">
|
||||
<time datetime={post.data.date.toISOString()}>
|
||||
|
||||
@@ -10,7 +10,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" title={`RSS Feed for strudel.cc`} href="/rss.xml" />
|
||||
<link rel="icon" type="image/svg+xml" href={`${baseNoTrailing}/favicon.ico`} />
|
||||
|
||||
<meta
|
||||
@@ -24,7 +24,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
|
||||
<base href={BASE_URL} />
|
||||
|
||||
<!-- Scrollable a11y code helper -->
|
||||
<script src{`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
|
||||
<script {`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
|
||||
|
||||
<script src="/src/pwa.ts"></script>
|
||||
<!-- this does not work for some reason: -->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { SITE, OPEN_GRAPH, Frontmatter } from '../config';
|
||||
import { SITE, OPEN_GRAPH, type Frontmatter } from '../config';
|
||||
|
||||
export interface Props {
|
||||
frontmatter: Frontmatter;
|
||||
@@ -25,11 +25,3 @@ const imageAlt = frontmatter.image?.alt ?? OPEN_GRAPH.image.alt;
|
||||
<meta property="og:image:alt" content={imageAlt} />
|
||||
<meta name="description" property="og:description" content={frontmatter.description ?? SITE.description} />
|
||||
<meta property="og:site_name" content={SITE.title} />
|
||||
|
||||
<!-- Twitter Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content={OPEN_GRAPH.twitter} />
|
||||
<meta name="twitter:title" content={formattedContentTitle} />
|
||||
<meta name="twitter:description" content={frontmatter.description ?? SITE.description} />
|
||||
<meta name="twitter:image" content={canonicalImageSrc} />
|
||||
<meta name="twitter:image:alt" content={imageAlt} />
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { loadFeaturedPatterns, loadPublicPatterns } from '@src/user_pattern_utils.mjs';
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
import { PatternLabel } from '@src/repl/panel/PatternsTab';
|
||||
|
||||
function PatternList({ patterns }) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* <MiniRepl tunes={patterns.map((pat) => pat.code.trim())} /> */}
|
||||
{patterns.map((pat) => (
|
||||
<div key={pat.id}>
|
||||
<div className="flex justify-between not-prose pb-2">
|
||||
<h2 className="text-lg">
|
||||
<a href={`/?${pat.hash}`} target="_blank" className="underline">
|
||||
<PatternLabel pattern={pat} />
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
<MiniRepl tune={pat.code.trim()} maxHeight={300} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Oven() {
|
||||
const [featuredPatterns, setFeaturedPatterns] = useState([]);
|
||||
const [publicPatterns, setPublicPatterns] = useState([]);
|
||||
useEffect(() => {
|
||||
loadPublicPatterns().then(({ data: pats }) => {
|
||||
console.log('pats', pats);
|
||||
setPublicPatterns(pats);
|
||||
});
|
||||
loadFeaturedPatterns().then(({ data: pats }) => {
|
||||
console.log('pats', pats);
|
||||
setFeaturedPatterns(pats);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div>
|
||||
<h2 id="featured">Featured Patterns</h2>
|
||||
<PatternList patterns={featuredPatterns} />
|
||||
<h2 id="latest">Last Creations</h2>
|
||||
<PatternList patterns={publicPatterns} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import LiteYouTubeEmbed from 'react-lite-youtube-embed';
|
||||
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css';
|
||||
|
||||
export function Youtube(props) {
|
||||
return <LiteYouTubeEmbed {...props} />;
|
||||
}
|
||||
@@ -58,6 +58,7 @@ export const SIDEBAR: Sidebar = {
|
||||
{ text: 'What is Strudel?', link: 'workshop/getting-started' },
|
||||
{ text: 'Showcase', link: 'intro/showcase' },
|
||||
{ text: 'Blog', link: 'blog' },
|
||||
{ text: 'Community Bakery', link: 'bakery' },
|
||||
],
|
||||
Workshop: [
|
||||
// { text: 'Getting Started', link: 'workshop/getting-started' },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'Release Notes v0.7.0 Zimtschnecke'
|
||||
title: 'Release Notes v0.7.0 Zuckerguss'
|
||||
description: ''
|
||||
date: '2023-03-23'
|
||||
tags: ['meta']
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
---
|
||||
title: '2 Years of Strudel'
|
||||
description: ''
|
||||
date: '2024-01-22'
|
||||
tags: ['meta']
|
||||
author: froos
|
||||
---
|
||||
|
||||
Strudel is now 2 years old! This was the first commit to the strudel repo:
|
||||
|
||||
```log
|
||||
commit 38b5a0d5cdf28685b2b5e18d460772b70246207b
|
||||
Author: Alex McLean <alex@slab.org>
|
||||
Date: Sat Jan 22 20:24:36 2022 +0000
|
||||
|
||||
Initial commit
|
||||
```
|
||||
|
||||
I have already written these 2 posts about the first year of strudel:
|
||||
|
||||
- [Introducing Strudel](https://loophole-letters.vercel.app/strudel)
|
||||
- [1 Year of Strudel](https://loophole-letters.vercel.app/strudel1year)
|
||||
|
||||
To continue that tradition, let me recap what happened around Strudel in 2023, at least what I have noticed:
|
||||
|
||||
## ICLC 2023
|
||||
|
||||
I presented strudel at the [International Conference on Live Coding (ICLC)](https://iclc.toplap.org/2023/), which took place in April in Utrecht.
|
||||
Here is the (pretty technical) video of my presentation:
|
||||
|
||||
import { Youtube } from '@src/components/Youtube';
|
||||
|
||||
<Youtube client:only="react" id="ZzDSW08IAdU" params="start=2805" />
|
||||
|
||||
For the conference, I wrote a paper with Alex McLean, called [Strudel: Live Coding Patterns on the Web](https://zenodo.org/records/7842142).
|
||||
For me personally, this was my first major contact with the live coding scene and it was a very memorable experience!
|
||||
|
||||
## Workshops and Courses
|
||||
|
||||
A lot of workshops have been using strudel in the last year, from many parts of the world:
|
||||
[Iceland](http://www.raflost.is/live-coding-for-beginners/), [UK](https://gutlevel.co.uk/whatson/patternclub8),
|
||||
[Japan](https://yoppa.org/kanazawa23), [France](https://www.stereolux.org/agenda/workshop-de-decouverte-de-la-musique-algorithmique-avec-strudel-avec-camille-amet) and the [US](https://www.nycresistor.com/2023/09/25/making-music-with-code-9-30-2023/) (probably more I've missed).
|
||||
I've also given workshops in [Switzerland](https://basel.codes/2023/index.html) and [Germany](https://www.cityofmediaarts.de/de/events/strudel-live-coding-workshop/), where we even had real strudel, thanks to [TOPLAP Karlsruhe](https://toplap-ka.de/)!
|
||||
|
||||

|
||||
|
||||
Additionally, there is now a [Creative Coding](https://www.chalmers.se/en/collaborate-with-us/activities-for-schools/creative-coding/) course at Chalmers in Sweden, consisting of "a 10-week series of workshops [...] for younger girls to have fun and experiment with code and music".
|
||||
I was very delighted to see this video of the girls having fun with Strudel:
|
||||
|
||||
<Youtube client:only="react" id="4zgHeNpG4wU" />
|
||||
|
||||
The course was also encompassed by a study on female empowerement through Creative Coding, read more in [Jasmina Maric's paper "Singing Code"](https://www.academia.edu/106106710/Singing_Code).
|
||||
|
||||
Another bigger installment was the [(Algo|Afro) Futures programme](https://algo-afro-futures.lurk.org/) led by Antonio Roberts, which is "a mentoring programme for early career Black artists who want to explore creative coding and live coding".
|
||||
For the final concert they recorded this amazing live stream:
|
||||
|
||||
<Youtube client:only="react" id="zUoZvkZ3J7Q" params="start=842" />
|
||||
|
||||
I am so happy to see that the project I've co-created has had such an impact already, making live coding workshops more easy and accessible.
|
||||
In my opinion, the reasons why people seem to choose strudel for teaching are:
|
||||
|
||||
- it does not require an installation, so it even runs on your phone / tablet or a chromebook
|
||||
- in-code and behind-code visualizations help learners to understand what the system is doing
|
||||
- the interactive documentation
|
||||
- the simple javascript syntax, which is very similar to [hydra](https://hydra.ojack.xyz)
|
||||
- tidal's mini notation for playful expression of rhythms (based on the [Bol Processor](https://bolprocessor.org/)'s polymetric expressions)
|
||||
- its range of functions for transforming patterns for those getting deeper into exploring strange rhythms
|
||||
|
||||
## Performances
|
||||
|
||||

|
||||
|
||||
People are starting to use Strudel for performances, many of which can be watched in the new [Showcase](/intro/showcase/)!
|
||||
The above photo comes from the [Mars College Department of Future Music](https://mars.college/gallery/live_coding/)
|
||||
|
||||
## New Features
|
||||
|
||||
Let's talk about some of the major features that landed since last year.
|
||||
|
||||
### Audio Engine Improved
|
||||
|
||||
The audio engine has gotten a lot of attention! Once integrated into Strudel itself, the engine has been pulled out and named [superdough](https://www.npmjs.com/package/superdough),
|
||||
the name being based on [SuperDirt](https://github.com/musikinformatik/SuperDirt), which is the audio engine of Tidal.
|
||||
Superdough is now also used by [topos](https://topos.live/), a creation of [Raphaël Forment](https://github.com/Bubobubobubobubo/), who helped me implement a bunch of new features.
|
||||
Lately, [Jade Rowland](https://github.com/daslyfe) has also developed many synthesis features into superdough! Here is a non-exhaustive list of new features:
|
||||
|
||||
- [filter envelopes](https://strudel.cc/learn/effects/#filter-envelope) + [24db filter option](https://strudel.cc/learn/effects/#ftype)
|
||||
- [pitch envelopes](https://strudel.cc/learn/effects/#pitch-envelope)
|
||||
- [fm synthesis](https://strudel.cc/learn/synths/#fm-synthesis)
|
||||
- [wavetable synthesis](https://strudel.cc/recipes/recipes/#wavetable-synthesis)
|
||||
- [phaser](https://strudel.cc/learn/effects/#phaser)
|
||||
- [vibrato](https://strudel.cc/learn/synths/#vibrato)
|
||||
- [noise](https://strudel.cc/learn/synths/#noise)
|
||||
- [an integration of the zzfx synth](https://strudel.cc/learn/synths/#zzfx)
|
||||
- [compressor](https://strudel.cc/learn/effects/#compressor)
|
||||
- multichannel audio / audio device selection
|
||||
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
|
||||
Here's an example that shows off filter envelopes:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`// "acidic tooth" @by eddyflux
|
||||
setcps(1)
|
||||
stack(
|
||||
note("[<g1 f1>/8](<3 5>,8)")
|
||||
.clip(perlin.range(.15,1.5))
|
||||
.release(.1)
|
||||
.s("sawtooth")
|
||||
.lpf(sine.range(400,800).slow(16))
|
||||
.lpq(cosine.range(6,14).slow(3))
|
||||
.lpenv(sine.mul(4).slow(4))
|
||||
.lpd(.2).lpa(.02)
|
||||
.ftype('24db')
|
||||
.rarely(add(note(12)))
|
||||
.room(.2).shape(.3).postgain(.5)
|
||||
.superimpose(x=>x.add(note(12)).delay(.5).bpf(1000))
|
||||
.gain("[.2 1@3]*2") // fake sidechain
|
||||
,
|
||||
stack(
|
||||
s("bd*2").mask("<0@4 1@16>"),
|
||||
s("hh*8").gain(saw.mul(saw.fast(2))).clip(sine)
|
||||
.mask("<0@8 1@16>")
|
||||
).bank('RolandTR909')
|
||||
)
|
||||
`}
|
||||
/>
|
||||
|
||||
### Import Sounds
|
||||
|
||||
In the "sounds" tab, you can now press "import sounds" to select sounds from your local disk, allowing you
|
||||
to use your own samples without the need to upload them! Keep in mind that if you share a pattern that uses local samples that the samples will fail to load on other people's machines..
|
||||
|
||||
### Community Bakery / Patterns Tab
|
||||
|
||||
You can now share your patterns publicly to the [Community Bakery](/bakery/), via the "Share" button!
|
||||
The Community Patterns are also listed in the new "patterns", where you can also save your own patterns locally!
|
||||
|
||||
### Quality of Life Improvements
|
||||
|
||||
You can now change the theme and font of the editor:
|
||||
|
||||

|
||||
|
||||
You can now enable autocompletion in the settings:
|
||||
|
||||

|
||||
|
||||
If you enable tooltips, you will get the same info by hovering the mouse over functions while holding the ctrl key!
|
||||
|
||||
Another handy thing is that Strudel now has offline support! Even samples will work once you have played them once. [More info](/learn/pwa/)
|
||||
|
||||
### New Visualizations
|
||||
|
||||
There is now a new `.spiral()` visualization that renders events in a spiral, elevating the cyclical nature of time (+ swirl also means Strudel in german).
|
||||
|
||||

|
||||
|
||||
Ah yes and you see one of the less readable fonts in this screenshot too :)
|
||||
|
||||
The `.scope()` function renders you an oscilloscope:
|
||||
|
||||

|
||||
|
||||
The pianoroll also got some more options, including the ability to set labels:
|
||||
|
||||

|
||||
|
||||
### Sliders
|
||||
|
||||
The `slider` is the first of a family of functions that render UI widgets inline:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<c eb g bb>*8")
|
||||
.s("sawtooth").lpenv(2).lpq(8)
|
||||
.lpf(slider(500, 100, 2000, 1))`}
|
||||
/>
|
||||
|
||||
Sliders are especially useful for gradual changes like fading things in and out by hand,
|
||||
which is not very practical with just code. The fact that the slider is inside the code and also changes the code itself,
|
||||
makes it feel connected to the rest of the system + doesn't distract or obstruct from the code.
|
||||
Other widgets will come for sure.
|
||||
|
||||
### More Voicings
|
||||
|
||||
One of my personal goals that got me involved with live coding in the first place is to be able to generate good sounding chord voicings on the fly.
|
||||
Strudel now supports most chord symbols:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`chord("<C^9 C7b9 Fm9 Db^7>/2")
|
||||
.dict('ireal').voicing()
|
||||
.s("sawtooth")
|
||||
.lpf(400).lpa(.5).lpenv(4)
|
||||
.phaser(4).room(.5)`}
|
||||
/>
|
||||
|
||||
So far, this is only [documented briefly](https://strudel.cc/learn/tonal/#voicing), a more in-depth guide will follow!
|
||||
|
||||
### Multiplayer / Collaborative Coding
|
||||
|
||||
The collaborative live coding editor [flok](https://flok.cc) had strudel support early on, thanks to [Damián Silvani](https://github.com/munshkr).
|
||||
Later, I've added [support for multiple strudel instances](https://github.com/munshkr/flok/pull/181), which was merged to the official version [just a few days ago](https://social.toplap.org/@munshkr/111748783047032711).
|
||||
This photo shows a flok jam I did with my workshop participants in basel:
|
||||
|
||||

|
||||
|
||||
The cool thing about flok is that you can jam with people on the other side of the planet!
|
||||
Here is a recording of the first session we organized over the discord server:
|
||||
|
||||
<Youtube client:only="react" id="p0J7XrT9JEs" params="start=2634" />
|
||||
|
||||
### Better MIDI integration
|
||||
|
||||
The midi integration has gotten a few new features:
|
||||
|
||||
- [clock out](https://github.com/tidalcycles/strudel/pull/710) to sync your midi devices / DAW to the strudel clock (better doc coming soon)
|
||||
- [cc output](https://strudel.cc/learn/input-output/#ccn--ccv) to send cc values to your gear
|
||||
- [cc input](https://github.com/tidalcycles/strudel/pull/699) to control strudel via MIDI (better doc coming soon)
|
||||
|
||||
Here is a little demo of me fiddling with a midi controller, changing a piano pattern:
|
||||
|
||||
<Youtube client:only="react" id="e2-Sv_jjDQk" />
|
||||
|
||||
### Experimental Features
|
||||
|
||||
- You can now run [hydra inside strudel](https://strudel.cc/learn/hydra/) + you can even run strudel in [hydra](https://hydra.ojack.xyz), thanks to [Olivia Jack](https://ojack.xyz/) and [Ámbar Tenorio Fornés](https://atenor.io/)! Read more [here](https://alpaca.pubpub.org/pub/b7hwrjfk/release/2?readingCollection=1def0192)
|
||||
- There is now a [VSCode Plugin](https://marketplace.visualstudio.com/items?itemName=roipoussiere.tidal-strudel) thanks to [roipoussiere](https://github.com/roipoussiere)! It allows you run patterns from a `.strudel` file inside VSCode.
|
||||
- Strudel can now be downloaded as a desktop app, thanks to [vasilymilovidov](https://github.com/vasilymilovidov) who has wrapped the REPL with [tauri](https://tauri.app/). You can download it [on the releases page](https://github.com/tidalcycles/strudel/releases) (scroll down to Assets). The performance is not optimal on MacOS and Linux, which is why it is still considered experimental
|
||||
|
||||
## Stats
|
||||
|
||||
Let's end the recap with some naked numbers:
|
||||
|
||||
### Releases
|
||||
|
||||
- 0.0.2 Schwindlig
|
||||
- 0.1.0 Stürmisch
|
||||
- 0.0.3 Maelstrom - state of last strudel post
|
||||
- 0.0.4 Gischt
|
||||
- 0.3.0 Donauwelle
|
||||
- 0.4.0 Brandung
|
||||
- 0.5.0 Wirbel
|
||||
- 0.6.0 Zimtschnecke - state after 1 year
|
||||
- 0.7.0 Zuckerguss
|
||||
- 0.8.0 Himbeermuffin
|
||||
- 0.9.0 Bananenbrot
|
||||
- 0.10.0 Kuchenboden
|
||||
- 0.11.0 Kuchengabel
|
||||
- 1.0.0 Geburtstagskuchen - now!
|
||||
|
||||
### Repo Stats
|
||||
|
||||
- 3399 commits (+1521)
|
||||
- 427 stars (+243)
|
||||
- 90 forks (+45)
|
||||
- 163 open issues (+110), 186 closed (+100)
|
||||
- 20 open PRs (+18), 515 closed (+292)
|
||||
- 32 contributors (+17)
|
||||
- 12 releases (+4)
|
||||
|
||||
## Thank you 💗
|
||||
|
||||
A big thanks to everyone contributing to this project in one way or another,
|
||||
be it by writing code, fixing typos, asking questions or making sounds!
|
||||
I am very grateful for how all of this came together and I'm looking forward to year 3!
|
||||
If you have some spare cash to support the tidal project, we'd be happy if you can [insert some coins here](https://opencollective.com/tidalcycles).
|
||||
@@ -1,4 +1,19 @@
|
||||
export function Icon({ type }) {
|
||||
if (type === 'skip') {
|
||||
// !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.
|
||||
return (
|
||||
<svg
|
||||
fillRule="evenodd"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="16"
|
||||
width="10"
|
||||
viewBox="0 0 320 512"
|
||||
>
|
||||
<path d="M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416V96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4l192 160L256 241V96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V271l-11.5 9.6-192 160z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
{
|
||||
@@ -31,6 +46,13 @@ export function Icon({ type }) {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
skip: (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416V96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4l192 160L256 241V96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V271l-11.5 9.6-192 160z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
}[type]
|
||||
}
|
||||
</svg>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||
import { Icon } from './Icon';
|
||||
import { silence, getPunchcardPainter, noteToMidi } from '@strudel/core';
|
||||
import { silence, getPunchcardPainter, noteToMidi, _mod } from '@strudel/core';
|
||||
import { transpiler } from '@strudel/transpiler';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel/webaudio';
|
||||
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
|
||||
import { StrudelMirror } from '@strudel/codemirror';
|
||||
// import { prebake } from '@strudel/repl';
|
||||
import { prebake } from '../repl/prebake.mjs';
|
||||
@@ -10,14 +10,16 @@ import { loadModules } from '../repl/util.mjs';
|
||||
import Claviature from '@components/Claviature';
|
||||
import useClient from '@src/useClient.mjs';
|
||||
|
||||
let prebaked, modulesLoading;
|
||||
let prebaked, modulesLoading, audioLoading;
|
||||
if (typeof window !== 'undefined') {
|
||||
prebaked = prebake();
|
||||
modulesLoading = loadModules();
|
||||
audioLoading = initAudioOnFirstClick();
|
||||
}
|
||||
|
||||
export function MiniRepl({
|
||||
tune: code,
|
||||
tune,
|
||||
tunes,
|
||||
hideHeader = false,
|
||||
canvasHeight = 100,
|
||||
onTrigger,
|
||||
@@ -25,7 +27,9 @@ export function MiniRepl({
|
||||
punchcardLabels = true,
|
||||
claviature,
|
||||
claviatureLabels,
|
||||
maxHeight,
|
||||
}) {
|
||||
const code = tunes ? tunes[0] : tune;
|
||||
const id = useMemo(() => s4(), []);
|
||||
const canvasId = useMemo(() => `canvas-${id}`, [id]);
|
||||
const shouldDraw = !!punchcard || !!claviature;
|
||||
@@ -75,7 +79,7 @@ export function MiniRepl({
|
||||
}
|
||||
return pat;
|
||||
},
|
||||
prebake: async () => Promise.all([modulesLoading, prebaked]),
|
||||
prebake: async () => Promise.all([modulesLoading, prebaked, audioLoading]),
|
||||
onUpdateState: (state) => {
|
||||
setReplState({ ...state });
|
||||
},
|
||||
@@ -91,6 +95,14 @@ export function MiniRepl({
|
||||
const containerRef = useRef();
|
||||
const client = useClient();
|
||||
|
||||
const [tuneIndex, setTuneIndex] = useState(0);
|
||||
const changeTune = (index) => {
|
||||
index = _mod(index, tunes.length);
|
||||
setTuneIndex(index);
|
||||
editorRef.current?.setCode(tunes[index]);
|
||||
editorRef.current?.evaluate();
|
||||
};
|
||||
|
||||
if (!client) {
|
||||
return <pre>{code}</pre>;
|
||||
}
|
||||
@@ -119,9 +131,31 @@ export function MiniRepl({
|
||||
<Icon type="refresh" />
|
||||
</button>
|
||||
</div>
|
||||
{tunes && (
|
||||
<div className="flex">
|
||||
<button
|
||||
className={
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
|
||||
}
|
||||
onClick={() => changeTune(tuneIndex - 1)}
|
||||
>
|
||||
<div className="rotate-180">
|
||||
<Icon type="skip" />
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
|
||||
}
|
||||
onClick={() => changeTune(tuneIndex + 1)}
|
||||
>
|
||||
<Icon type="skip" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-auto relative p-1">
|
||||
<div className="overflow-auto relative p-1" style={maxHeight ? { maxHeight: `${maxHeight}px` } : {}}>
|
||||
<div
|
||||
ref={(el) => {
|
||||
if (!editorRef.current) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
export const examples = [
|
||||
`// "coastline" @by eddyflux
|
||||
await samples('github:eddyflux/crate')
|
||||
setcps(.75)
|
||||
let chords = chord("<Bbm9 Fm9>/4").dict('ireal')
|
||||
stack(
|
||||
stack( // DRUMS
|
||||
s("bd").struct("<[x*<1 2> [~@3 x]] x>"),
|
||||
s("~ [rim, sd:<2 3>]").room("<0 .2>"),
|
||||
n("[0 <1 3>]*<2!3 4>").s("hh"),
|
||||
s("rd:<1!3 2>*2").mask("<0 0 1 1>/16").gain(.5)
|
||||
).bank('crate')
|
||||
.mask("<[0 1] 1 1 1>/16".early(.5))
|
||||
, // CHORDS
|
||||
chords.offset(-1).voicing().s("gm_epiano1:1")
|
||||
.phaser(4).room(.5)
|
||||
, // MELODY
|
||||
n("<0!3 1*2>").set(chords).mode("root:g2")
|
||||
.voicing().s("gm_acoustic_bass"),
|
||||
chords.n("[0 <4 3 <2 5>>*2](<3 5>,8)")
|
||||
.set(x).anchor("D5").voicing()
|
||||
.segment(4).clip(rand.range(.4,.8))
|
||||
.room(.75).shape(.3).delay(.25)
|
||||
.fm(sine.range(3,8).slow(8))
|
||||
.lpf(sine.range(500,1000).slow(8)).lpq(5)
|
||||
.rarely(ply("2")).chunk(4, fast(2))
|
||||
.gain(perlin.range(.6, .9))
|
||||
.mask("<0 1 1 0>/16")
|
||||
)
|
||||
.late("[0 .01]*4").late("[0 .01]*2").size(4)`,
|
||||
`// "broken cut 1" @by froos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
samples({
|
||||
'slap': 'https://cdn.freesound.org/previews/495/495416_10350281-lq.mp3',
|
||||
'whirl': 'https://cdn.freesound.org/previews/495/495313_10350281-lq.mp3',
|
||||
'attack': 'https://cdn.freesound.org/previews/494/494947_10350281-lq.mp3'
|
||||
})
|
||||
|
||||
setcps(1.25)
|
||||
|
||||
note("[c2 ~](3,8)*2,eb,g,bb,d").s("sawtooth")
|
||||
.noise(0.3)
|
||||
.lpf(perlin.range(800,2000).mul(0.6))
|
||||
.lpenv(perlin.range(1,5)).lpa(.25).lpd(.1).lps(0)
|
||||
.add.mix(note("<0!3 [1 <4!3 12>]>")).late(.5)
|
||||
.vib("4:.2")
|
||||
.room(1).roomsize(4).slow(4)
|
||||
.stack(
|
||||
s("bd").late("<0.01 .251>"),
|
||||
s("breaks165:1/2").fit()
|
||||
.chop(4).sometimesBy(.4, ply("2"))
|
||||
.sometimesBy(.1, ply("4")).release(.01)
|
||||
.gain(1.5).sometimes(mul(speed("1.05"))).cut(1)
|
||||
,
|
||||
s("<whirl attack>?").delay(".8:.1:.8").room(2).slow(8).cut(2),
|
||||
).reset("<x@30 [x*[8 [8 [16 32]]]]@2>".late(2))`,
|
||||
`// "acidic tooth" @by eddyflux
|
||||
setcps(1)
|
||||
stack(
|
||||
note("[<g1 f1>/8](<3 5>,8)")
|
||||
.clip(perlin.range(.15,1.5))
|
||||
.release(.1)
|
||||
.s("sawtooth")
|
||||
.lpf(sine.range(400,800).slow(16))
|
||||
.lpq(cosine.range(6,14).slow(3))
|
||||
.lpenv(sine.mul(4).slow(4))
|
||||
.lpd(.2).lpa(.02)
|
||||
.ftype('24db')
|
||||
.rarely(add(note(12)))
|
||||
.room(.2).shape(.3).postgain(.5)
|
||||
.superimpose(x=>x.add(note(12)).delay(.5).bpf(1000))
|
||||
.gain("[.2 1@3]*2") // fake sidechain
|
||||
,
|
||||
stack(
|
||||
s("bd*2").mask("<0@4 1@16>"),
|
||||
s("hh*8").gain(saw.mul(saw.fast(2))).clip(sine)
|
||||
.mask("<0@8 1@16>")
|
||||
).bank('RolandTR909')
|
||||
)`,
|
||||
];
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
import HeadCommon from '../components/HeadCommon.astro';
|
||||
import Header from '../components/Header/Header.astro';
|
||||
import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
|
||||
import PageContent from '../components/PageContent/PageContent.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { compareDesc } from 'date-fns';
|
||||
import { Oven as CommunityOven } from '../components/Oven/Oven.jsx';
|
||||
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
|
||||
|
||||
const currentPage = Astro.url.pathname;
|
||||
|
||||
const posts = (await getCollection('blog')).sort((a, b) => compareDesc(a.data.date, b.data.date));
|
||||
---
|
||||
|
||||
<html dir={'ltr'} lang={'en'} class="initial dark">
|
||||
<head>
|
||||
<HeadCommon />
|
||||
<title>🌀 Strudel Community Bakery</title>
|
||||
</head>
|
||||
|
||||
<body class="h-app-height text-gray-50 bg-background">
|
||||
<div class="w-full h-full space-y-4 flex flex-col">
|
||||
<header class="max-w-full fixed top-0 w-full z-[100]">
|
||||
<Header currentPage={currentPage} />
|
||||
</header>
|
||||
<main class="relative pt-16">
|
||||
<div class="h-full top-0 overflow-auto min-w-[300px] flex xl:justify-center pr-4 pl-4 md:pl-[300px] xl:pl-0">
|
||||
<aside title="Site Navigation" class="w-[300px] px-6 left-0 hidden md:block fixed h-full">
|
||||
<LeftSidebar currentPage={currentPage} />
|
||||
</aside>
|
||||
<PageContent>
|
||||
<h1>Community Bakery</h1>
|
||||
<p>
|
||||
This page contains all the strudel patterns baked by the community. Add your own by clicking the "Share"
|
||||
button in the REPL. Have fun, and please share some of what you create with the community.
|
||||
</p>
|
||||
<CommunityOven client:only />
|
||||
</PageContent>
|
||||
<aside class="fixed right-0 h-full overflow-auto pr-4 pl-0 pb-16 hidden xl:block" title="Table of Contents">
|
||||
<RightSidebar
|
||||
headings={[
|
||||
{
|
||||
depth: 1,
|
||||
slug: 'featured',
|
||||
text: 'Featured',
|
||||
},
|
||||
{
|
||||
depth: 1,
|
||||
slug: 'latest',
|
||||
text: 'Last Creations',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,7 +8,7 @@ import PageContent from '../components/PageContent/PageContent.astro';
|
||||
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { compareDesc } from 'date-fns';
|
||||
|
||||
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css';
|
||||
const currentPage = Astro.url.pathname;
|
||||
|
||||
const posts = (await getCollection('blog')).sort((a, b) => compareDesc(a.data.date, b.data.date));
|
||||
@@ -32,7 +32,18 @@ const posts = (await getCollection('blog')).sort((a, b) => compareDesc(a.data.da
|
||||
<LeftSidebar currentPage={currentPage} />
|
||||
</aside>
|
||||
<PageContent>
|
||||
{posts.map((post) => <BlogPost post={post} />)}
|
||||
<div class="border-b-4 border-lineHighlight py-4">
|
||||
<h1>Strudel Blog</h1>
|
||||
<p>
|
||||
Welcome to the Strudel Blog, where we will keep you updated with the latest changes and things happening
|
||||
in the strudelsphere. You can subscribe to this blog using <a target="_blank" href="/rss.xml"
|
||||
>this rss link</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-8">
|
||||
{posts.map((post) => <BlogPost post={post} />)}
|
||||
</div>
|
||||
</PageContent>
|
||||
<aside class="fixed right-0 h-full overflow-auto pr-4 pl-0 pb-16 hidden xl:block" title="Table of Contents">
|
||||
<RightSidebar
|
||||
|
||||
@@ -33,7 +33,7 @@ While the Mini Notation is a powerful way to write rhythms concisely, it also ha
|
||||
client:idle
|
||||
tune={`stack(
|
||||
note("c2 eb2(3,8)").s('sawtooth').cutoff(800),
|
||||
s("bd,~ sd,hh*4")
|
||||
s("bd(5,8), hh*8")
|
||||
)`}
|
||||
/>
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
<JsDoc client:idle name="Pattern.chunkBack" h={0} />
|
||||
|
||||
### fastChunk
|
||||
|
||||
<JsDoc client:idle name="Pattern.fastChunk" h={0} />
|
||||
|
||||
## arp
|
||||
|
||||
<JsDoc client:idle name="Pattern#arp" h={0} />
|
||||
|
||||
@@ -150,7 +150,7 @@ Pitch envelopes can breathe life into static sounds:
|
||||
.s("gm_electric_guitar_jazz")
|
||||
.penv("<.5 0 7 -2>*2").vib("4:.1")
|
||||
.phaser(2).delay(.25).room(.3)
|
||||
.size(4).fast(.75)`}
|
||||
.size(4).fast(1.5)`}
|
||||
/>
|
||||
|
||||
You also create some lovely chiptune-style sounds:
|
||||
@@ -163,7 +163,7 @@ You also create some lovely chiptune-style sounds:
|
||||
.voicing().add(note("<0 1>/8"))
|
||||
.dec(.1).room(.2)
|
||||
.segment("<4 [2 8]>")
|
||||
.penv("<0 <2 -2>>").patt(.02)`}
|
||||
.penv("<0 <2 -2>>").patt(.02).fast(2)`}
|
||||
/>
|
||||
|
||||
Let's break down all pitch envelope controls:
|
||||
|
||||
@@ -36,7 +36,8 @@ osc(1, -0.9, 300)
|
||||
.scale(2)
|
||||
.out()
|
||||
|
||||
note("[a,c,e,<a4 ab4 g4 gb4>,b4]/4").s("sawtooth").vib(2)
|
||||
note("[a,c,e,<a4 ab4 g4 gb4>,b4]/2")
|
||||
.s("sawtooth").vib(2)
|
||||
.lpf(600).lpa(2).lpenv(6)
|
||||
`}
|
||||
/>
|
||||
@@ -90,7 +91,7 @@ src(s0).kaleid(H("<4 5 6>"))
|
||||
.out()
|
||||
//
|
||||
stack(
|
||||
s("bd*2,[hh:0:<.5 1>]*4,~ rim").bank("RolandTR909").speed(.9),
|
||||
s("bd*4,[hh:0:<.5 1>]*8,~ rim").bank("RolandTR909").speed(.9),
|
||||
note("[<g1!3 <bb1 <f1 d1>>>]*3").s("sawtooth")
|
||||
.room(.75).sometimes(add(note(12))).clip(.3)
|
||||
.lpa(.05).lpenv(-4).lpf(2000).lpq(8).ftype('24db')
|
||||
|
||||
@@ -22,27 +22,25 @@ Before diving deeper into the details, here is a flavour of how the Mini-Notatio
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`note(\`[
|
||||
[
|
||||
[e5 [b4 c5] d5 [c5 b4]]
|
||||
[a4 [a4 c5] e5 [d5 c5]]
|
||||
[b4 [~ c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||
[e5 [~ c5] e5 [d5 c5]]
|
||||
[b4 [b4 c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
],[
|
||||
[[e2 e3]*4]
|
||||
[[a2 a3]*4]
|
||||
[[g#2 g#3]*2 [e2 e3]*2]
|
||||
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||
[[d2 d3]*4]
|
||||
[[c2 c3]*4]
|
||||
[[b1 b2]*2 [e2 e3]*2]
|
||||
[[a1 a2]*4]
|
||||
]
|
||||
]/16\`)`}
|
||||
tune={`note(\`<
|
||||
[e5 [b4 c5] d5 [c5 b4]]
|
||||
[a4 [a4 c5] e5 [d5 c5]]
|
||||
[b4 [~ c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||
[e5 [~ c5] e5 [d5 c5]]
|
||||
[b4 [b4 c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
,
|
||||
[[e2 e3]*4]
|
||||
[[a2 a3]*4]
|
||||
[[g#2 g#3]*2 [e2 e3]*2]
|
||||
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||
[[d2 d3]*4]
|
||||
[[c2 c3]*4]
|
||||
[[b1 b2]*2 [e2 e3]*2]
|
||||
[[a1 a2]*4]
|
||||
>\`)`}
|
||||
/>
|
||||
|
||||
## Mini Notation Format
|
||||
@@ -74,9 +72,21 @@ Taking the two examples above, we have four and eight events respectively, and s
|
||||
This is perhaps counter-intuitive if you are used to adding notes in a sequencer or piano roll and the overall length increasing.
|
||||
But, it will begin to make sense as we go through more elements of mini-notation.
|
||||
|
||||
## Multiplication
|
||||
|
||||
A sequence can be sped up by multiplying it by a number using the asterisk symbol (`*`):
|
||||
|
||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2")`} punchcard />
|
||||
|
||||
The multiplication by two here means that the sequence will play twice per cycle.
|
||||
|
||||
Multiplications can also be decimal (`*2.75`):
|
||||
|
||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2.75")`} punchcard />
|
||||
|
||||
## Division
|
||||
|
||||
We can slow the sequence down by enclosing it in brackets and dividing it by a number (`/2`):
|
||||
Contrary to multiplication, division can slow the sequence down by enclosing it in brackets and dividing it by a number (`/2`):
|
||||
|
||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]/2")`} punchcard />
|
||||
|
||||
@@ -102,18 +112,11 @@ The advantage of the angle brackets, is that we can add more events without need
|
||||
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5 e5 b4>")`} punchcard />
|
||||
|
||||
This is more similar to traditional music sequencers and piano rolls, where adding a note increases the perceived overall duration.
|
||||
We can also play a certain number of notes per cycle by using angle brackets with multiplication:
|
||||
|
||||
## Multiplication
|
||||
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5 a4 c5>*8")`} punchcard />
|
||||
|
||||
Contrary to division, a sequence can be sped up by multiplying it by a number using the asterisk symbol (`*`):
|
||||
|
||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2")`} punchcard />
|
||||
|
||||
The multiplication by two here means that the sequence will play twice per cycle.
|
||||
|
||||
As with divisions, multiplications can be decimal (`*2.75`):
|
||||
|
||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2.75")`} punchcard />
|
||||
Now we are playing 8 notes per cycle!
|
||||
|
||||
## Subdividing time with bracket nesting
|
||||
|
||||
@@ -149,13 +152,13 @@ The following are the same:
|
||||
|
||||
But to play multiple chords in a sequence, we have to wrap them in brackets:
|
||||
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>")`} punchcard />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>*2")`} punchcard />
|
||||
|
||||
## Elongation
|
||||
|
||||
With the "@" symbol, we can specify temporal "weight" of a sequence child:
|
||||
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>")`} punchcard />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>*2")`} punchcard />
|
||||
|
||||
Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
|
||||
|
||||
@@ -163,19 +166,19 @@ Here, the first chord has a weight of 2, making it twice the length of the other
|
||||
|
||||
Using "!" we can repeat without speeding up:
|
||||
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")`} punchcard />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>*2")`} punchcard />
|
||||
|
||||
## Mini-notation review
|
||||
|
||||
To recap what we've learned so far, compare the following patterns:
|
||||
|
||||
<MiniRepl client:idle tune={`note("<g3 b3 e4 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]/2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]*2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] _ [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
<MiniRepl client:idle tune={`note("<g3 b3 e4 [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]/2 [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]*2 [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] _ [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
|
||||
## Euclidian rhythms
|
||||
|
||||
@@ -196,7 +199,7 @@ But using the Euclidian rhythm notation, we only need to express "3 beats over 8
|
||||
|
||||
This makes it easy to write patterns with interesting rhythmic structures and variations that still sound familiar:
|
||||
|
||||
<MiniRepl client:idle tune={`note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(4)`} punchcard canvasHeight={50} />
|
||||
<MiniRepl client:idle tune={`note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(2)`} punchcard canvasHeight={50} />
|
||||
|
||||
Note that since the example above does not use the third `offset` parameter, it can be written simply as `"(3,8)"`.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Strudel allows loading samples in the form of audio files of various formats (wa
|
||||
|
||||
By default, strudel comes with a built-in "sample map", providing a solid base to play with.
|
||||
|
||||
<MiniRepl client:idle tune={`s("bd sd,hh*8, misc/2")`} />
|
||||
<MiniRepl client:idle tune={`s("bd sd [~ bd] sd,hh*16, misc")`} />
|
||||
|
||||
Here, we are using the `s` function to play back different default samples (`bd`, `sd`, `hh` and `misc`) to get a drum beat.
|
||||
|
||||
@@ -59,15 +59,15 @@ If we open the `sounds` tab and then `drum machines`, we can see that the drum s
|
||||
|
||||
We _could_ use them like this:
|
||||
|
||||
<MiniRepl client:idle tune={`s("RolandTR808_bd RolandTR808_sd,RolandTR808_hh*8")`} />
|
||||
<MiniRepl client:idle tune={`s("RolandTR808_bd RolandTR808_sd,RolandTR808_hh*16")`} />
|
||||
|
||||
... but thats obviously a bit much to write. Using the `bank` function, we can shorten this to:
|
||||
|
||||
<MiniRepl client:idle tune={`s("bd sd,hh*8").bank("RolandTR808")`} />
|
||||
<MiniRepl client:idle tune={`s("bd sd bd sd,hh*16").bank("RolandTR808")`} />
|
||||
|
||||
You could even pattern the bank to switch between different drum machines:
|
||||
|
||||
<MiniRepl client:idle tune={`s("bd sd,hh*8").bank("<RolandTR808 RolandTR909>")`} />
|
||||
<MiniRepl client:idle tune={`s("bd sd bd sd,hh*16").bank("RolandTR808 RolandTR909")`} />
|
||||
|
||||
Behind the scenes, `bank` will just prepend the drum machine name to the sample name with `_` to get the full name.
|
||||
This of course only works because the name after `_` (`bd`, `sd` etc..) is standardized.
|
||||
@@ -79,17 +79,21 @@ If we open the `sounds` tab again, followed by tab `drum machines`, there is als
|
||||
For example `RolandTR909_hh(4)` means there are 4 samples of a TR909 hihat available.
|
||||
By default, `s` will play the first sample, but we can select the other ones using `n`, starting from 0:
|
||||
|
||||
<MiniRepl client:idle tune={`s("hh*4").bank("RolandTR909").n("<0 1 2 3>")`} />
|
||||
<MiniRepl client:idle tune={`s("hh*8").bank("RolandTR909").n("0 1 2 3")`} />
|
||||
|
||||
Numbers that are too high will just wrap around to the beginning
|
||||
|
||||
<MiniRepl client:idle tune={`s("hh*4").bank("RolandTR909").n("<0 1 2 3 4 5 6 7>")`} />
|
||||
<MiniRepl client:idle tune={`s("hh*8").bank("RolandTR909").n("0 1 2 3 4 5 6 7")`} />
|
||||
|
||||
Here, 0-3 will play the same sounds as 4-7, because `RolandTR909_hh` only has 4 sounds.
|
||||
|
||||
Selecting sounds also works inside the mini notation, using "`:`" like this:
|
||||
|
||||
<MiniRepl client:idle tune={`s("bd:1 bd:2,hh:0 hh:1 hh:2 hh:3").bank("RolandTR909")`} />
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`s("bd*4,hh:0 hh:1 hh:2 hh:3 hh:4 hh:5 hh:6 hh:7")
|
||||
.bank("RolandTR909")`}
|
||||
/>
|
||||
|
||||
# Loading Custom Samples
|
||||
|
||||
@@ -103,7 +107,7 @@ In this example we create a map using sounds from the default sample map:
|
||||
sd: 'sd/rytm-01-classic.wav',
|
||||
hh: 'hh27/000_hh27closedhh.wav',
|
||||
}, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
||||
s("bd sd,hh*8")`}
|
||||
s("bd sd bd sd,hh*16")`}
|
||||
/>
|
||||
|
||||
When you load your own samples, you can choose the names that you will then refer to in your pattern string inside the `s` function.
|
||||
@@ -116,7 +120,7 @@ Compare with this example which uses the same samples, but with different names.
|
||||
snaredrum: 'sd/rytm-01-classic.wav',
|
||||
hihat: 'hh27/000_hh27closedhh.wav',
|
||||
}, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
||||
s("bassdrum snaredrum, hihat*8")`}
|
||||
s("bassdrum snaredrum bassdrum snaredrum, hihat*16")`}
|
||||
/>
|
||||
|
||||
Here we have changed the "map" to include longer sample names.
|
||||
@@ -140,7 +144,7 @@ Because GitHub is a popular place for uploading open source samples, it has its
|
||||
sd: 'sd/rytm-01-classic.wav',
|
||||
hh: 'hh27/000_hh27closedhh.wav',
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
s("bd sd,hh*8")`}
|
||||
s("bd sd bd sd,hh*16")`}
|
||||
/>
|
||||
|
||||
The format is `github:user/repo/branch/`.
|
||||
@@ -157,7 +161,7 @@ We can see there are some guitar samples inside the `/samples` folder, so let's
|
||||
g3: 'samples/guitar/guitar_3.wav',
|
||||
g4: 'samples/guitar/guitar_4.wav'
|
||||
}, 'github:jarmitage/jarmitage.github.io/master/');
|
||||
s("[g0 g1 g2 g3 g4]/5")`}
|
||||
s("<g0 g1 g2 g3 g4>/2")`}
|
||||
/>
|
||||
|
||||
## Multiple Samples per Sound
|
||||
@@ -171,7 +175,7 @@ It is also possible, to declare multiple files for one sound, using the array no
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
s("<bd:0 bd:1>,~ <sd:0 sd:1>,[hh:0 hh:1]*2")`}
|
||||
s("bd:0 bd:1,~ <sd:0 sd:1> ~ sd:0,[hh:0 hh:1]*4")`}
|
||||
/>
|
||||
|
||||
The `:0` `:1` etc. are the indices of the array.
|
||||
@@ -184,7 +188,7 @@ The sample number can also be set using `n`:
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
s("bd,~ sd,hh*4").n("<0 1>")`}
|
||||
s("bd bd,~ sd ~ sd,hh*8").n("<0 1>")`}
|
||||
/>
|
||||
|
||||
In that case, we might load our guitar sample map a different way:
|
||||
@@ -200,7 +204,7 @@ In that case, we might load our guitar sample map a different way:
|
||||
'samples/guitar/guitar_4.wav'
|
||||
]
|
||||
}, 'github:jarmitage/jarmitage.github.io/master/');
|
||||
s("[guitar:0 guitar:1 guitar:2 guitar:3 guitar:4]/5")`}
|
||||
s("<guitar:0 guitar:1 guitar:2 guitar:3 guitar:4>*2")`}
|
||||
/>
|
||||
|
||||
And as above, we can choose the sample number using `n` for even more flexibility:
|
||||
@@ -216,7 +220,7 @@ And as above, we can choose the sample number using `n` for even more flexibilit
|
||||
'samples/guitar/guitar_4.wav'
|
||||
]
|
||||
}, 'github:jarmitage/jarmitage.github.io/master/');
|
||||
n("<0 1 2 3 4>").s("guitar")`}
|
||||
n("<0 1 2 3 4>*2").s("guitar")`}
|
||||
/>
|
||||
|
||||
## Pitched Sounds
|
||||
@@ -271,7 +275,7 @@ We can also declare different samples for different regions of the keyboard:
|
||||
}}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
note("g2!2 <bb2 c3>!2, <c4@3 [<eb4 bb3> g4 f4]>")
|
||||
.s('moog').clip(1)
|
||||
.gain(.5)`}
|
||||
.gain(.5).cpm(60)`}
|
||||
/>
|
||||
|
||||
The sampler will always pick the closest matching sample for the current note!
|
||||
@@ -285,9 +289,9 @@ With it, you can enter any sample name(s) to query from [freesound.org](https://
|
||||
client:idle
|
||||
tune={`await samples('shabda:bass:4,hihat:4,rimshot:2')
|
||||
stack(
|
||||
n("0 1 2 3").s('bass').slow(2),
|
||||
n("0 1 2 3 0 1 2 3").s('bass'),
|
||||
n("0 1*2 2 3*2").s('hihat'),
|
||||
n("~ 0 ~ 1").s('rimshot')
|
||||
n("~ 0 ~ 1 ~ 0 0 1").s('rimshot')
|
||||
).clip(1)`}
|
||||
/>
|
||||
|
||||
@@ -299,8 +303,8 @@ Note that the language code and the gender parameters are optional and default t
|
||||
tune={`await samples('shabda/speech:the_drum,forever')
|
||||
await samples('shabda/speech/fr-FR/m:magnifique')
|
||||
stack(
|
||||
s("the_drum").chop(16).speed(rand.range(0.85,1.1)),
|
||||
s("forever magnifique").slow(8).late(0.25)
|
||||
s("the_drum*2").chop(16).speed(rand.range(0.85,1.1)),
|
||||
s("forever magnifique").slow(4).late(0.125)
|
||||
)`}
|
||||
/>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can b
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`note("c2 <eb2 <g2 g1>>")
|
||||
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
|
||||
.sound("<sawtooth square triangle sine>")
|
||||
.scope()`}
|
||||
/>
|
||||
@@ -28,7 +28,7 @@ If you don't set a `sound` but a `note` the default value for `sound` is `triang
|
||||
You can also use noise as a source by setting the waveform to: `white`, `pink` or `brown`. These are different
|
||||
flavours of noise, here written from hard to soft.
|
||||
|
||||
<MiniRepl client:idle tune={`sound("<white pink brown>/2").scope()`} />
|
||||
<MiniRepl client:idle tune={`sound("<white pink brown>").scope()`} />
|
||||
|
||||
Here's a more musical example of how to use noise for hihats:
|
||||
|
||||
@@ -44,7 +44,7 @@ Some amount of pink noise can also be added to any oscillator by using the `nois
|
||||
|
||||
You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter:
|
||||
|
||||
<MiniRepl client:idle tune={`s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)).scope()`} />
|
||||
<MiniRepl client:idle tune={`s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(2)).scope()`} />
|
||||
|
||||
### Additive Synthesis
|
||||
|
||||
@@ -52,7 +52,7 @@ To tame the harsh sound of the basic waveforms, we can set the `n` control to li
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`note("c2 <eb2 <g2 g1>>")
|
||||
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
|
||||
.sound("sawtooth")
|
||||
.n("<32 16 8 4>")
|
||||
.scope()`}
|
||||
@@ -63,7 +63,7 @@ You can also set `n` directly in mini notation with `sound`:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`note("c2 <eb2 <g2 g1>>")
|
||||
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
|
||||
.sound("sawtooth:<32 16 8 4>")
|
||||
.scope()`}
|
||||
/>
|
||||
@@ -125,7 +125,7 @@ note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")
|
||||
.n("<1 2 3 4 5 6 7 8 9 10>/2").room(0.5).size(0.9)
|
||||
.s('wt_flute').velocity(0.25).often(n => n.ply(2))
|
||||
.release(0.125).decay("<0.1 0.25 0.3 0.4>").sustain(0)
|
||||
.cutoff(2000).scope({}).cutoff("<1000 2000 4000>").fast(2)`}
|
||||
.cutoff(2000).scope({}).cutoff("<1000 2000 4000>").fast(4)`}
|
||||
/>
|
||||
|
||||
## ZZFX
|
||||
@@ -137,8 +137,8 @@ It has 20 parameters in total, here is a snippet that uses all:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`note("<c2 eb2 f2 g2>") // also supports freq
|
||||
.s("<z_sawtooth z_tan z_noise z_sine z_square>")
|
||||
tune={`note("c2 eb2 f2 g2") // also supports freq
|
||||
.s("{z_sawtooth z_tan z_noise z_sine z_square}%4")
|
||||
.zrand(0) // randomization
|
||||
// zzfx envelope
|
||||
.attack(0.001)
|
||||
|
||||
@@ -14,6 +14,19 @@ These functions use [tonaljs](https://github.com/tonaljs/tonal) to provide helpe
|
||||
|
||||
<JsDoc client:idle name="voicing" h={0} />
|
||||
|
||||
Here's an example of how you can play chords and a bassline:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`chord("<C^7 A7b13 Dm7 G7>*2")
|
||||
.dict('ireal').layer(
|
||||
x=>x.struct("[~ x]*2").voicing()
|
||||
,
|
||||
x=>n("0*4").set(x).mode("root:g2").voicing()
|
||||
.s('sawtooth').cutoff("800:4:2")
|
||||
)`}
|
||||
/>
|
||||
|
||||
### scale(name)
|
||||
|
||||
<JsDoc client:idle name="scale" h={0} />
|
||||
@@ -22,13 +35,13 @@ These functions use [tonaljs](https://github.com/tonaljs/tonal) to provide helpe
|
||||
|
||||
Transposes all notes to the given number of semitones:
|
||||
|
||||
<MiniRepl client:only="react" tune={`"c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).note()`} />
|
||||
<MiniRepl client:only="react" tune={`"[c2 c3]*4".transpose("<0 -2 5 3>").note()`} />
|
||||
|
||||
This method gets really exciting when we use it with a pattern as above.
|
||||
|
||||
Instead of numbers, scientific interval notation can be used as well:
|
||||
|
||||
<MiniRepl client:only="react" tune={`"c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()`} />
|
||||
<MiniRepl client:only="react" tune={`"[c2 c3]*4".transpose("<1P -2M 4P 3m>").note()`} />
|
||||
|
||||
### scaleTranspose(steps)
|
||||
|
||||
@@ -36,9 +49,9 @@ Transposes notes inside the scale by the number of steps:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`"-8 [2,4,6]"
|
||||
tune={`"[-8 [2,4,6]]*2"
|
||||
.scale('C4 bebop major')
|
||||
.scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>")
|
||||
.scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>*2")
|
||||
.note()`}
|
||||
/>
|
||||
|
||||
@@ -46,22 +59,28 @@ Transposes notes inside the scale by the number of steps:
|
||||
|
||||
Turns chord symbols into voicings, using the smoothest voice leading possible:
|
||||
|
||||
<MiniRepl client:only="react" tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()`} />
|
||||
<MiniRepl
|
||||
client:only="react"
|
||||
tune={`stack(
|
||||
"<C^7 A7 Dm7 G7>".voicings('lefthand'),
|
||||
"<C3 A2 D3 G2>"
|
||||
).note()`}
|
||||
/>
|
||||
|
||||
Note: This function might be removed, as `voicing` (without s) is a newer implementation.
|
||||
|
||||
### rootNotes(octave = 2)
|
||||
|
||||
Turns chord symbols into root notes of chords in given octave.
|
||||
|
||||
<MiniRepl client:only="react" tune={`"<C^7 A7b13 Dm7 G7>".rootNotes(3).note()`} />
|
||||
<MiniRepl client:only="react" tune={`"<C^7 A7b13 Dm7 G7>*2".rootNotes(3).note()`} />
|
||||
|
||||
Together with layer, struct and voicings, this can be used to create a basic backing track:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`"<C^7 A7b13 Dm7 G7>".layer(
|
||||
x => x.voicings('lefthand').struct("~ x").note(),
|
||||
tune={`"<C^7 A7b13 Dm7 G7>*2".layer(
|
||||
x => x.voicings('lefthand').struct("[~ x]*2").note(),
|
||||
x => x.rootNotes(2).note().s('sawtooth').cutoff(800)
|
||||
)`}
|
||||
/>
|
||||
|
||||
So far, we've stayed within the browser. [MIDI and OSC](/learn/input-output/) are ways to break out of it.
|
||||
|
||||
@@ -61,7 +61,7 @@ A sample can be looped and chopped like this:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen/8").fit().chop(16)`}
|
||||
s("amen/4").fit().chop(32)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -72,9 +72,9 @@ Let's add randmized doubling + reversing:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen/8").fit().chop(16).cut(1)
|
||||
.sometimesBy(.5, ply(2))
|
||||
.sometimesBy(.25, mul(speed(-1)))`}
|
||||
s("amen/4").fit().chop(16).cut(1)
|
||||
.sometimesBy(.5, ply("2"))
|
||||
.sometimesBy(.25, mul(speed("-1")))`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -83,9 +83,9 @@ If we want to specify the order of samples, we can replace `chop` with `slice`:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen/8").fit()
|
||||
.slice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
|
||||
.cut(1).rarely(ply(2))`}
|
||||
s("amen/4").fit()
|
||||
.slice(8, "<0 1 2 3 4*2 5 6 [6 7]>*2")
|
||||
.cut(1).rarely(ply("2"))`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -95,8 +95,8 @@ If we use `splice` instead of `slice`, the speed adjusts to the duration of the
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen")
|
||||
.splice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
|
||||
.cut(1).rarely(ply(2))`}
|
||||
.splice(8, "<0 1 2 3 4*2 5 6 [6 7]>*2")
|
||||
.cut(1).rarely(ply("2"))`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -104,26 +104,38 @@ Note that we don't need `fit`, because `splice` will do that by itself.
|
||||
|
||||
## Filter Envelopes
|
||||
|
||||
A minimal filter envelope looks like this:
|
||||
Using `lpenv`, we can make the filter move:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("g1 bb1 <c2 eb2> d2")
|
||||
.s("sawtooth")
|
||||
.lpf(400).lpa(.2).lpenv(4)
|
||||
.lpf(400).lpenv(4)
|
||||
.scope()`}
|
||||
/>
|
||||
|
||||
We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`:
|
||||
The type of envelope depends on the methods you're setting. Let's set `lpa`:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("g1 bb1 <c2 eb2> d2")
|
||||
.s("sawtooth").lpq(8)
|
||||
.lpf(400).lpa(.2).lpenv(-4)
|
||||
.lpf(400).lpa(.2).lpenv(4)
|
||||
.scope()`}
|
||||
/>
|
||||
|
||||
Now the filter is attacking, rather than decaying as before (decay is the default). We can also do both
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("g1 bb1 <c2 eb2> d2")
|
||||
.s("sawtooth").lpq(8)
|
||||
.lpf(400).lpa(.1).lpd(.1).lpenv(4)
|
||||
.scope()`}
|
||||
/>
|
||||
|
||||
You can play around with `lpa` | `lpd` | `lps` | `lpd` to see what the filter envelope will do.
|
||||
|
||||
## Layering Sounds
|
||||
|
||||
We can layer sounds by separating them with ",":
|
||||
@@ -183,7 +195,7 @@ A polyrhythm is when 2 different tempos happen at the same time.
|
||||
|
||||
This is a polymeter:
|
||||
|
||||
<MiniRepl client:visible tune={`s("<bd rim>,<hh hh oh>").fast(2)`} punchcard />
|
||||
<MiniRepl client:visible tune={`s("<bd rim, hh hh oh>*4")`} punchcard />
|
||||
|
||||
A polymeter is when 2 different bar lengths play at the same tempo.
|
||||
|
||||
@@ -201,8 +213,8 @@ Using `run` with `n`, we can rush through a sample bank:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||
n(run(8)).s("ftabla")`}
|
||||
tune={`await samples('bubo:fox')
|
||||
n(run(8)).s("ftabla")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -212,17 +224,17 @@ In this case, I hear the beginning at the third sample, which can be accounted f
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||
n(run(8)).s("ftabla").early(2/8)`}
|
||||
tune={`await samples('bubo:fox')
|
||||
n(run(8)).s("ftabla").early(2/8)`}
|
||||
/>
|
||||
|
||||
Let's add some randomness:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||
tune={`await samples('bubo:fox')
|
||||
n(run(8)).s("ftabla").early(2/8)
|
||||
.sometimes(mul(speed(1.5)))`}
|
||||
.sometimes(mul(speed("1.5")))`}
|
||||
/>
|
||||
|
||||
## Tape Warble
|
||||
@@ -231,7 +243,7 @@ We can emulate a pitch warbling effect like this:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("c4 bb f eb")
|
||||
tune={`note("<c4 bb f eb>*8")
|
||||
.add(note(perlin.range(0,.5))) // <------ warble
|
||||
.clip(2).s("gm_electric_guitar_clean")`}
|
||||
/>
|
||||
@@ -243,7 +255,7 @@ There are a number of ways to change the sound duration. Using clip:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("f ab bb c")
|
||||
.clip("<2 1 .5 .25>/2")`}
|
||||
.clip("<2 1 .5 .25>")`}
|
||||
/>
|
||||
|
||||
The value of clip is relative to the duration of each event.
|
||||
@@ -252,20 +264,18 @@ We can also create overlaps using release:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("f ab bb c")
|
||||
.release("<2 1 .5 .002>/2")`}
|
||||
.release("<2 1 .5 .25>")`}
|
||||
/>
|
||||
|
||||
This will smoothly fade out each sound for the given number of seconds.
|
||||
We could also make the notes shorter with decay / sustain:
|
||||
We could also make the notes shorter by using a decay envelope:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("f ab bb c")
|
||||
.decay("<.2 .1 .02>/2").sustain(0)`}
|
||||
.decay("<2 1 .5 .25>")`}
|
||||
/>
|
||||
|
||||
For now, there is a limitation where decay values that exceed the event duration may cause little cracks, so use higher numbers with caution..
|
||||
|
||||
When using samples, we also have `.end` to cut relative to the sample length:
|
||||
|
||||
<MiniRepl client:visible tune={`s("oh*4").end("<1 .5 .25 .1>")`} />
|
||||
@@ -274,9 +284,9 @@ Compare that to clip:
|
||||
|
||||
<MiniRepl client:visible tune={`s("oh*4").clip("<1 .5 .25 .1>")`} />
|
||||
|
||||
or decay / sustain
|
||||
or decay:
|
||||
|
||||
<MiniRepl client:visible tune={`s("oh*4").decay("<.2 .12 .06 .01>").sustain(0)`} />
|
||||
<MiniRepl client:visible tune={`s("oh*4").decay("<1 .5 .25 .1>")`} />
|
||||
|
||||
## Wavetable Synthesis
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import rss from '@astrojs/rss';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
export async function GET(context) {
|
||||
const posts = (await getCollection('blog')).filter((p) => !p.data.draft);
|
||||
const options = {
|
||||
title: 'Strudel Blog',
|
||||
description:
|
||||
'The Strudel Blog will keep you updated with the latest changes and things happening in the strudelsphere.',
|
||||
site: context.site,
|
||||
items: posts.map((post) => ({
|
||||
link: `/blog/#${post.slug}`,
|
||||
title: post.data.title,
|
||||
pubDate: post.data.date,
|
||||
description: post.data.description,
|
||||
})),
|
||||
};
|
||||
return rss(options);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ These packages provide bindings for different ways to output strudel patterns:
|
||||
|
||||
### No Longer Maintained
|
||||
|
||||
- [react](https://github.com/tidalcycles/strudel/tree/main/packages/react#strudelreact): react hooks and components for strudel
|
||||
- [react](https://www.npmjs.com/package/@strudel.cycles/react): react hooks and components for strudel
|
||||
- [eval](https://www.npmjs.com/package/@strudel.cycles/eval): old code transpiler
|
||||
- [tone](https://www.npmjs.com/package/@strudel.cycles/tone): bindings for Tone.js instruments and effects
|
||||
- [webdirt](https://www.npmjs.com/package/@strudel.cycles/webdirt): webdirt bindings, replaced by webaudio package
|
||||
|
||||
@@ -15,20 +15,20 @@ Strudel's mother language, TidalCycles, even has it in its name.
|
||||
## Cycles and BPM
|
||||
|
||||
In most music software, the unit BPM (beats per minute) is used to set the tempo.
|
||||
Strudel expresses tempo as CPS (cycles per second), with a default of 1CPS:
|
||||
Strudel expresses tempo as CPS (cycles per second), with a default of 0.5 CPS:
|
||||
|
||||
<MiniRepl client:visible tune={`s("bd")`} />
|
||||
|
||||
Here we can hear the 1CPS in action: The kick repeats once per second like a clock.
|
||||
We could say 1CPS = 1BPS (beats per second) = 60BPM. Let's add another kick:
|
||||
Here we can hear the 0.5CPS in action: The kick repeats once every two seconds.
|
||||
Let's make it 4 kicks:
|
||||
|
||||
<MiniRepl client:visible tune={`s("bd bd")`} />
|
||||
<MiniRepl client:visible tune={`s("bd bd bd bd")`} />
|
||||
|
||||
Now we have 2 kicks per second, but the whole pattern still plays at 1CPS.
|
||||
Now we have 4 kicks per cycle, but the whole pattern still plays at 0.5CPS.
|
||||
In terms of BPM, most musicians would tell you this is playing at 120bpm.
|
||||
What about this one:
|
||||
|
||||
<MiniRepl client:visible tune={`s("bd hh")`} />
|
||||
<MiniRepl client:visible tune={`s("bd hh bd hh")`} />
|
||||
|
||||
Because the second sound is now a hihat, the tempo feels slower again.
|
||||
This brings us to an important realization:
|
||||
@@ -103,7 +103,7 @@ To get a time signature, just change the number of elements per bar. Here is a r
|
||||
|
||||
or with 5:
|
||||
|
||||
<MiniRepl client:visible tune={`s("<bd hh hh bd hh hh bd rim bd hh>*5")`} />
|
||||
<MiniRepl client:visible tune={`s("bd hh hh bd hh hh bd rim bd hh")`} />
|
||||
|
||||
We could also write multiple bars with different time signatures:
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ The upper limit decreases with age. What's your upper limit?
|
||||
|
||||
In Strudel, we can play frequencies directly with the `freq` control:
|
||||
|
||||
<MiniRepl client:visible tune={`freq("200 [300,500] 400 [500,<600 670 712 670>]")`} />
|
||||
<MiniRepl client:visible tune={`freq("<200 [300,500] 400 [500,<600 670 712 670>]>*8")`} />
|
||||
|
||||
## Frequency vs Pitch Perception
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ We have sounds, we have notes, now let's look at effects!
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
|
||||
.sound("sawtooth").lpf(800)`}
|
||||
/>
|
||||
|
||||
@@ -35,8 +35,8 @@ lpf = **l**ow **p**ass **f**ilter
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
.sound("sawtooth").lpf("200 1000")`}
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
|
||||
.sound("sawtooth").lpf("200 1000 200 1000")`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
@@ -52,8 +52,8 @@ We will learn how to automate with waves later...
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
|
||||
.sound("sawtooth").vowel("<a e i o>/2")`}
|
||||
tune={`note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>")
|
||||
.sound("sawtooth").vowel("<a e i o>")`}
|
||||
/>
|
||||
|
||||
**gain**
|
||||
@@ -61,8 +61,8 @@ We will learn how to automate with waves later...
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
sound("hh*8").gain("[.25 1]*2"),
|
||||
sound("bd*2,~ sd:1")
|
||||
sound("hh*16").gain("[.25 1]*4"),
|
||||
sound("bd*4,[~ sd:1]*2")
|
||||
) `}
|
||||
punchcard
|
||||
/>
|
||||
@@ -84,13 +84,13 @@ Let's combine all of the above into a little tune:
|
||||
client:visible
|
||||
tune={`stack(
|
||||
stack(
|
||||
sound("hh*8").gain("[.25 1]*2"),
|
||||
sound("bd*2,~ sd:1")
|
||||
sound("hh*8").gain("[.25 1]*4"),
|
||||
sound("bd*4,[~ sd:1]*2")
|
||||
),
|
||||
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
.sound("sawtooth").lpf("200 1000"),
|
||||
note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
|
||||
.sound("sawtooth").vowel("<a e i o>/2")
|
||||
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
|
||||
.sound("sawtooth").lpf("200 1000 200 1000"),
|
||||
note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>")
|
||||
.sound("sawtooth").vowel("<a e i o>")
|
||||
) `}
|
||||
/>
|
||||
|
||||
@@ -105,7 +105,7 @@ The 3 parts (drums, bassline, chords) are exactly as earlier, just stacked toget
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<c3 bb2 f3 eb3>")
|
||||
tune={`note("c3 bb2 f3 eb3")
|
||||
.sound("sawtooth").lpf(600)
|
||||
.attack(.1)
|
||||
.decay(.1)
|
||||
@@ -141,7 +141,7 @@ Can you guess what they do?
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<c3 bb2 f3 eb3>")
|
||||
tune={`note("c3 bb2 f3 eb3")
|
||||
.sound("sawtooth").lpf(600)
|
||||
.adsr(".1:.1:.5:.2")
|
||||
`}
|
||||
@@ -152,7 +152,7 @@ Can you guess what they do?
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
|
||||
.sound("gm_electric_guitar_muted"),
|
||||
sound("<bd rim>").bank("RolandTR707")
|
||||
).delay(".5")`}
|
||||
@@ -182,7 +182,7 @@ What happens if you use `.delay(".8:.06:.8")` ? Can you guess what the third num
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
tune={`n("<4 [3@3 4] [<2 0> ~@16] ~>")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2)`}
|
||||
/>
|
||||
@@ -200,10 +200,10 @@ Add a delay too!
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
|
||||
.sound("gm_electric_guitar_muted").delay(.5),
|
||||
sound("<bd rim>").bank("RolandTR707").delay(.5),
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2).gain(.5)
|
||||
)`}
|
||||
@@ -214,13 +214,13 @@ Let's add a bass to make this complete:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
|
||||
.sound("gm_electric_guitar_muted").delay(.5),
|
||||
sound("<bd rim>").bank("RolandTR707").delay(.5),
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2).gain(.4),
|
||||
n("<0 [~ 0] 4 [3 2] [0 ~] [0 ~] <0 2> ~>*2")
|
||||
n("[0 [~ 0] 4 [3 2] [0 ~] [0 ~] <0 2> ~]/2")
|
||||
.scale("D2:minor")
|
||||
.sound("sawtooth,triangle").lpf(800)
|
||||
)`}
|
||||
@@ -237,19 +237,18 @@ Try adding `.hush()` at the end of one of the patterns in the stack...
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`sound("numbers:1 numbers:2 numbers:3 numbers:4")
|
||||
.pan("0 0.3 .6 1")
|
||||
.slow(2)`}
|
||||
.pan("0 0.3 .6 1")`}
|
||||
/>
|
||||
|
||||
**speed**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd rim").speed("<1 2 -1 -2>").room(.2)`} />
|
||||
<MiniRepl client:visible tune={`sound("bd rim [~ bd] rim").speed("<1 2 -1 -2>").room(.2)`} />
|
||||
|
||||
**fast and slow**
|
||||
|
||||
We can use `fast` and `slow` to change the tempo of a pattern outside of Mini-Notation:
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd*2,~ rim").slow(2)`} />
|
||||
<MiniRepl client:visible tune={`sound("bd*4,~ rim ~ cp").slow(2)`} />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -261,13 +260,13 @@ What happens if you use a pattern like `.fast("<1 [2 4]>")`?
|
||||
|
||||
By the way, inside Mini-Notation, `fast` is `*` and `slow` is `/`.
|
||||
|
||||
<MiniRepl client:visible tune={`sound("[bd*2,~ rim]*<1 [2 4]>")`} />
|
||||
<MiniRepl client:visible tune={`sound("[bd*4,~ rim ~ cp]*<1 [2 4]>")`} />
|
||||
|
||||
## automation with signals
|
||||
|
||||
Instead of changing values stepwise, we can also control them with signals:
|
||||
|
||||
<MiniRepl client:visible tune={`sound("hh*16").gain(sine)`} punchcard punchcardLabels={false} />
|
||||
<MiniRepl client:visible tune={`sound("hh*32").gain(sine)`} punchcard punchcardLabels={false} />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -283,7 +282,7 @@ The gain is visualized as transparency in the pianoroll.
|
||||
|
||||
By default, waves oscillate between 0 to 1. We can change that with `range`:
|
||||
|
||||
<MiniRepl client:visible tune={`sound("hh*8").lpf(saw.range(500, 2000))`} />
|
||||
<MiniRepl client:visible tune={`sound("hh*16").lpf(saw.range(500, 2000))`} />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -295,9 +294,9 @@ We can change the automation speed with slow / fast:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
|
||||
.sound("sawtooth")
|
||||
.lpf(sine.range(100, 2000).slow(8))`}
|
||||
.lpf(sine.range(100, 2000).slow(4))`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
@@ -308,15 +307,15 @@ The whole automation will now take 8 cycles to repeat.
|
||||
|
||||
## Recap
|
||||
|
||||
| name | example |
|
||||
| ----- | --------------------------------------------------------------------------------------- |
|
||||
| lpf | <MiniRepl client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
|
||||
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
|
||||
| gain | <MiniRepl client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
|
||||
| delay | <MiniRepl client:visible tune={`s("bd rim").delay(.5)`} /> |
|
||||
| room | <MiniRepl client:visible tune={`s("bd rim").room(.5)`} /> |
|
||||
| pan | <MiniRepl client:visible tune={`s("bd rim").pan("0 1")`} /> |
|
||||
| speed | <MiniRepl client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
|
||||
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
|
||||
| name | example |
|
||||
| ----- | ---------------------------------------------------------------------------------------- |
|
||||
| lpf | <MiniRepl client:visible tune={`note("c2 c3 c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
|
||||
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
|
||||
| gain | <MiniRepl client:visible tune={`s("hh*16").gain("[.25 1]*2")`} /> |
|
||||
| delay | <MiniRepl client:visible tune={`s("bd rim bd cp").delay(.5)`} /> |
|
||||
| room | <MiniRepl client:visible tune={`s("bd rim bd cp").room(.5)`} /> |
|
||||
| pan | <MiniRepl client:visible tune={`s("bd rim bd cp").pan("0 1")`} /> |
|
||||
| speed | <MiniRepl client:visible tune={`s("bd rim bd cp").speed("<1 2 -1 -2>")`} /> |
|
||||
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
|
||||
|
||||
Let us now take a look at some of Tidal's typical [pattern effects](/workshop/pattern-effects).
|
||||
|
||||
@@ -175,11 +175,11 @@ Try adding more notes inside the brackets and notice how it does **not** get fas
|
||||
|
||||
**Play one sequence per cycle**
|
||||
|
||||
{/* <[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2 */}
|
||||
{/* <[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4> */}
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<[36 48]*4 [34 46]*4 [41 53]*4 [39 51]*4>/2")
|
||||
tune={`note("<[36 48]*4 [34 46]*4 [41 53]*4 [39 51]*4>")
|
||||
.sound("gm_acoustic_bass")`}
|
||||
punchcard
|
||||
/>
|
||||
@@ -197,7 +197,7 @@ This is also useful for unpitched sounds:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
tune={`sound("bd*4, [~ <sd cp>]*2, [~ hh]*4")
|
||||
.bank("RolandTR909")`}
|
||||
punchcard
|
||||
/>
|
||||
@@ -209,7 +209,7 @@ Finding the right notes can be difficult.. Scales are here to help:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`n("0 2 4 <[6,8] [7,9]>")
|
||||
.scale("C:minor").sound("piano")`}
|
||||
.scale("C:minor").sound("piano").cpm(60)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -236,7 +236,7 @@ Just like anything, we can automate the scale with a pattern:
|
||||
client:visible
|
||||
tune={`n("<0 -3>, 2 4 <[6,8] [7,9]>")
|
||||
.scale("<C:major D:mixolydian>/4")
|
||||
.sound("piano")`}
|
||||
.sound("piano").cpm(60)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -269,7 +269,7 @@ Try changing that number!
|
||||
client:visible
|
||||
tune={`n("<[4@2 4] [5@2 5] [6@2 6] [5@2 5]>*2")
|
||||
.scale("<C2:mixolydian F2:mixolydian>/4")
|
||||
.sound("gm_acoustic_bass")`}
|
||||
.sound("gm_acoustic_bass").cpm(60)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -283,7 +283,7 @@ This is also sometimes called triplet swing. You'll often find it in blues and j
|
||||
|
||||
**Replicate**
|
||||
|
||||
<MiniRepl client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano")`} punchcard />
|
||||
<MiniRepl client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano").cpm(60)`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -300,7 +300,7 @@ Let's recap what we've learned in this chapter:
|
||||
| Concept | Syntax | Example |
|
||||
| --------- | ------ | -------------------------------------------------------- |
|
||||
| Slow down | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
|
||||
| Alternate | \<\> | <MiniRepl client:visible tune={`note("c <e g>")`} /> |
|
||||
| Alternate | \<\> | <MiniRepl client:visible tune={`note("c a f <e g>")`} /> |
|
||||
| Elongate | @ | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
|
||||
| Replicate | ! | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
|
||||
|
||||
@@ -318,7 +318,7 @@ New functions:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
|
||||
.sound("gm_synth_bass_1")
|
||||
.lpf(800) // <-- we'll learn about this soon`}
|
||||
/>
|
||||
@@ -332,7 +332,7 @@ New functions:
|
||||
[~ 0] 1 [0 1] [~ 1]
|
||||
[~ 0] 3 [0 3] [~ 3]
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
>*2\`).scale("C4:minor")
|
||||
>*4\`).scale("C4:minor")
|
||||
.sound("gm_synth_strings_1")`}
|
||||
/>
|
||||
|
||||
@@ -340,7 +340,7 @@ New functions:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
tune={`sound("bd*4, [~ <sd cp>]*2, [~ hh]*4")
|
||||
.bank("RolandTR909")`}
|
||||
/>
|
||||
|
||||
@@ -355,16 +355,16 @@ It's called `stack` 😙
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
|
||||
.sound("gm_synth_bass_1").lpf(800),
|
||||
n(\`<
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
[~ 0] 1 [0 1] [~ 1]
|
||||
[~ 0] 3 [0 3] [~ 3]
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
>*2\`).scale("C4:minor")
|
||||
>*4\`).scale("C4:minor")
|
||||
.sound("gm_synth_strings_1"),
|
||||
sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
sound("bd*4, [~ <sd cp>]*2, [~ hh]*4")
|
||||
.bank("RolandTR909")
|
||||
)`}
|
||||
/>
|
||||
|
||||
@@ -147,11 +147,11 @@ We will look at other ways to change the tempo later!
|
||||
|
||||
**Add a rests in a sequence with '~'**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd hh ~ rim")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("bd hh ~ rim ~ bd hh rim")`} punchcard />
|
||||
|
||||
**Sub-Sequences with [brackets]**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd [hh hh] sd [hh bd]")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("bd [hh hh] sd [hh bd] bd ~ [hh sd] cp")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -163,15 +163,15 @@ Similar to the whole sequence, the content of a sub-sequence will be squished to
|
||||
|
||||
**Multiplication: Speed things up**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd hh*2 rim hh*3")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("bd hh*2 rim hh*3 bd [~ hh*2] rim hh*2")`} punchcard />
|
||||
|
||||
**Multiplication: Speed up sequences**
|
||||
**Multiplication: Speed up subsequences**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd [hh rim]*2")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("bd [hh rim]*2 bd [hh rim]*1.5")`} punchcard />
|
||||
|
||||
**Multiplication: Speeeeeeeeed things up**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd hh*16 rim hh*8")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("bd hh*32 rim hh*16")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -181,7 +181,7 @@ Pitch = really fast rhythm
|
||||
|
||||
**Sub-Sub-Sequences with [[brackets]]**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd [[rim rim] hh]")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("bd [[rim rim] hh] bd cp")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -234,34 +234,39 @@ This is shorter and more readable than:
|
||||
Now we've learned the basics of the so called Mini-Notation, the rhythm language of Tidal.
|
||||
This is what we've leared so far:
|
||||
|
||||
| Concept | Syntax | Example |
|
||||
| ----------------- | -------- | --------------------------------------------------------------------- |
|
||||
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sd hh")`} /> |
|
||||
| Sample Number | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
|
||||
| Rests | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
|
||||
| Sub-Sequences | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
|
||||
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
|
||||
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
|
||||
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
|
||||
| Concept | Syntax | Example |
|
||||
| ----------------- | -------- | ----------------------------------------------------------------------- |
|
||||
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sd hh")`} /> |
|
||||
| Sample Number | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
|
||||
| Rests | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
|
||||
| Sub-Sequences | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
|
||||
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz [sd cp]]]")`} /> |
|
||||
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
|
||||
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
|
||||
|
||||
The Mini-Notation is usually used inside some function. These are the functions we've seen so far:
|
||||
|
||||
| Name | Description | Example |
|
||||
| ----- | ----------------------------------- | ----------------------------------------------------------------------- |
|
||||
| sound | plays the sound of the given name | <MiniRepl client:visible tune={`sound("bd sd")`} /> |
|
||||
| bank | selects the sound bank | <MiniRepl client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
|
||||
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
|
||||
| n | select sample number | <MiniRepl client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
|
||||
| Name | Description | Example |
|
||||
| ----- | ----------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| sound | plays the sound of the given name | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd")`} /> |
|
||||
| bank | selects the sound bank | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").bank("RolandTR909")`} /> |
|
||||
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").cpm(45)`} /> |
|
||||
| n | select sample number | <MiniRepl client:visible tune={`n("0 1 4 2 0 6 3 2").sound("jazz")`} /> |
|
||||
|
||||
## Examples
|
||||
|
||||
**Basic rock beat**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd sd, hh*4").bank("RolandTR505").cpm(100/2)`} punchcard />
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`sound("[bd sd]*2, hh*8").bank("RolandTR505")
|
||||
.cpm(100/4)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Classic house**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("bd*2, ~ cp, [~ hh]*2").bank("RolandTR909")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("bd*4, [~ cp]*2, [~ hh]*4").bank("RolandTR909")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -279,7 +284,7 @@ We Will Rock you
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`sound("bd sd, ~ ~ ~ hh ~ hh ~ ~, ~ perc ~ perc:1*2")
|
||||
.bank("RolandCompurhythm1000")`}
|
||||
.bank("RolandCompurhythm1000").cpm(120/2)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import { examples } from '../../examples.mjs';
|
||||
|
||||
# Welcome
|
||||
|
||||
@@ -29,40 +30,13 @@ The best place to actually make music with Strudel is the [Strudel REPL](https:/
|
||||
- teaching: focussing on a low barrier of entry, Strudel is a good fit for teaching music and code at the same time.
|
||||
- integrate into your existing music setup: either via MIDI or OSC, you can use Strudel as a really flexible sequencer
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
Here is an example of how strudel can sound:
|
||||
Here are some examples of how strudel can sound:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`stack(
|
||||
// drums
|
||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8")
|
||||
.speed(perlin.range(.8,.9)), // random sample speed variation
|
||||
// bassline
|
||||
"<a1 b1\*2 a1(3,8) e2>"
|
||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||
.note() // wrap in "note"
|
||||
.decay(.15).sustain(0) // make each note of equal length
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.4) // turn down
|
||||
.cutoff(sine.slow(7).range(300,5000)), // automate cutoff
|
||||
// chords
|
||||
"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand')
|
||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.note() // wrap in "note"
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.16) // turn down
|
||||
.cutoff(500) // fixed cutoff
|
||||
.attack(1) // slowly fade in
|
||||
)
|
||||
.slow(3/2)`}
|
||||
/>
|
||||
<MiniRepl client:idle tunes={examples} />
|
||||
|
||||
To hear more, go to the [Strudel REPL](https://strudel.cc/) and press shuffle to hear a random example pattern.
|
||||
These examples cannot fully encompass the variety of things you can do, so [check out the showcase](/intro/showcase/) for some videos of how people use Strudel.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -15,19 +15,19 @@ In this chapter, we are going to look at functions that are more unique to tidal
|
||||
|
||||
**reverse patterns with rev**
|
||||
|
||||
<MiniRepl client:visible tune={`n("0 1 [4 3] 2").sound("jazz").rev()`} />
|
||||
<MiniRepl client:visible tune={`n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").rev()`} />
|
||||
|
||||
**play pattern left and modify it right with jux**
|
||||
|
||||
<MiniRepl client:visible tune={`n("0 1 [4 3] 2").sound("jazz").jux(rev)`} />
|
||||
<MiniRepl client:visible tune={`n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").jux(rev)`} />
|
||||
|
||||
This is the same as:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(0),
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(1).rev()
|
||||
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(0),
|
||||
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(1).rev()
|
||||
)`}
|
||||
/>
|
||||
|
||||
@@ -36,8 +36,8 @@ Let's visualize what happens here:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(0).color("cyan"),
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(1).color("magenta").rev()
|
||||
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(0).color("cyan"),
|
||||
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(1).color("magenta").rev()
|
||||
)`}
|
||||
punchcard
|
||||
/>
|
||||
@@ -50,16 +50,16 @@ Try commenting out one of the two by adding `//` before a line
|
||||
|
||||
**multiple tempos**
|
||||
|
||||
<MiniRepl client:visible tune={`note("c2, eb3 g3 [bb3 c4]").sound("piano").slow("1,2,3")`} />
|
||||
<MiniRepl client:visible tune={`note("c2, eb3 g3 [bb3 c4]").sound("piano").slow("0.5,1,1.5")`} />
|
||||
|
||||
This is like doing
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1).color('cyan'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(2).color('magenta'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(3).color('yellow')
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(0.5).color('cyan'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1).color('magenta'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1.5).color('yellow')
|
||||
)`}
|
||||
punchcard
|
||||
/>
|
||||
@@ -74,9 +74,9 @@ Try commenting out one or more by adding `//` before a line
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>"))
|
||||
tune={`note("c2 [eb3,g3] ".add("<0 <1 -1>>"))
|
||||
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
|
||||
.sound("gm_acoustic_bass").room(.5)`}
|
||||
.sound("gm_acoustic_bass").room(.5).cpm(60)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -92,7 +92,7 @@ We can add as often as we like:
|
||||
client:visible
|
||||
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>").add("0,7"))
|
||||
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
|
||||
.sound("gm_acoustic_bass").room(.5)`}
|
||||
.sound("gm_acoustic_bass").room(.5).cpm(60)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
@@ -100,7 +100,7 @@ We can add as often as we like:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
|
||||
tune={`n("0 [2 4] <3 5> [~ <4 1>]".add("<0 [0,2,4]>"))
|
||||
.scale("C5:minor").release(.5)
|
||||
.sound("gm_xylophone").room(.5)`}
|
||||
punchcard
|
||||
@@ -111,7 +111,7 @@ We can add as often as we like:
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
|
||||
n("0 [2 4] <3 5> [~ <4 1>]".add("<0 [0,2,4]>"))
|
||||
.scale("C5:minor")
|
||||
.sound("gm_xylophone")
|
||||
.room(.4).delay(.125),
|
||||
@@ -119,17 +119,17 @@ We can add as often as we like:
|
||||
.adsr("[.1 0]:.2:[1 0]")
|
||||
.sound("gm_acoustic_bass")
|
||||
.room(.5),
|
||||
n("0 1 [2 3] 2").sound("jazz").jux(rev).slow(2)
|
||||
n("0 1 [2 3] 2").sound("jazz").jux(rev)
|
||||
)`}
|
||||
/>
|
||||
|
||||
**ply**
|
||||
|
||||
<MiniRepl client:visible tune={`sound("hh, bd rim").bank("RolandTR707").ply(2)`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("hh hh, bd rim [~ cp] rim").bank("RolandTR707").ply(2)`} punchcard />
|
||||
|
||||
this is like writing:
|
||||
|
||||
<MiniRepl client:visible tune={`sound("hh*2, bd*2 rim*2").bank("RolandTR707")`} punchcard />
|
||||
<MiniRepl client:visible tune={`sound("hh*2 hh*2, bd*2 rim*2 [~ cp*2] rim*2").bank("RolandTR707")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -141,10 +141,10 @@ Try patterning the `ply` function, for example using `"<1 2 1 3>"`
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`n("<0 [4 <3 2>] <2 3> [~ 1]>"
|
||||
.off(1/8, x=>x.add(4))
|
||||
//.off(1/4, x=>x.add(7))
|
||||
).scale("<C5:minor Db5:mixolydian>/4")
|
||||
tune={`n("0 [4 <3 2>] <2 3> [~ 1]"
|
||||
.off(1/16, x=>x.add(4))
|
||||
//.off(1/8, x=>x.add(7))
|
||||
).scale("<C5:minor Db5:mixolydian>/2")
|
||||
.s("triangle").room(.5).ds(".1:0").delay(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
@@ -159,14 +159,14 @@ off is also useful for sounds:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`s("bd sd,[~ hh]*2").bank("CasioRZ1")
|
||||
.off(1/8, x=>x.speed(1.5).gain(.25))`}
|
||||
tune={`s("bd sd [rim bd] sd,[~ hh]*4").bank("CasioRZ1")
|
||||
.off(1/16, x=>x.speed(1.5).gain(.25))`}
|
||||
/>
|
||||
|
||||
| name | description | example |
|
||||
| ---- | ------------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| rev | reverse | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
|
||||
| jux | split left/right, modify right | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
|
||||
| add | add numbers / notes | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
|
||||
| ply | speed up each event n times | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
|
||||
| off | copy, shift time & modify | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
|
||||
| name | description | example |
|
||||
| ---- | ------------------------------ | ------------------------------------------------------------------------------------------- |
|
||||
| rev | reverse | <MiniRepl client:visible tune={`n("0 2 4 6 ~ 7 9 5").scale("C:minor").rev()`} /> |
|
||||
| jux | split left/right, modify right | <MiniRepl client:visible tune={`n("0 2 4 6 ~ 7 9 5").scale("C:minor").jux(rev)`} /> |
|
||||
| add | add numbers / notes | <MiniRepl client:visible tune={`n("0 2 4 6 ~ 7 9 5".add("<0 1 2 1>")).scale("C:minor")`} /> |
|
||||
| ply | speed up each event n times | <MiniRepl client:visible tune={`s("bd sd [~ bd] sd").ply("<1 2 3>")`} /> |
|
||||
| off | copy, shift time & modify | <MiniRepl client:visible tune={`s("bd sd [~ bd] sd, hh*8").off(1/16, x=>x.speed(2))`} /> |
|
||||
|
||||
@@ -13,12 +13,12 @@ This page is just a listing of all functions covered in the workshop!
|
||||
|
||||
| Concept | Syntax | Example |
|
||||
| ----------------- | -------- | --------------------------------------------------------------------- |
|
||||
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sn hh")`} /> |
|
||||
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sd hh bd cp sd hh")`} /> |
|
||||
| Sample Number | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
|
||||
| Rests | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
|
||||
| Sub-Sequences | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
|
||||
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sn]]")`} /> |
|
||||
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sn*2 cp*3")`} /> |
|
||||
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
|
||||
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
|
||||
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
|
||||
| Slow down | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
|
||||
| Alternate | \<\> | <MiniRepl client:visible tune={`note("c <e g>")`} /> |
|
||||
@@ -45,22 +45,22 @@ This page is just a listing of all functions covered in the workshop!
|
||||
|
||||
| name | example |
|
||||
| ----- | --------------------------------------------------------------------------------------- |
|
||||
| lpf | <MiniRepl client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
|
||||
| lpf | <MiniRepl client:visible tune={`note("c2 c3 c2 c3").s("sawtooth").lpf("400 2000")`} /> |
|
||||
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
|
||||
| gain | <MiniRepl client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
|
||||
| delay | <MiniRepl client:visible tune={`s("bd rim").delay(.5)`} /> |
|
||||
| room | <MiniRepl client:visible tune={`s("bd rim").room(.5)`} /> |
|
||||
| pan | <MiniRepl client:visible tune={`s("bd rim").pan("0 1")`} /> |
|
||||
| speed | <MiniRepl client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
|
||||
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
|
||||
| gain | <MiniRepl client:visible tune={`s("hh*16").gain("[.25 1]*4")`} /> |
|
||||
| delay | <MiniRepl client:visible tune={`s("bd rim bd cp").delay(.5)`} /> |
|
||||
| room | <MiniRepl client:visible tune={`s("bd rim bd cp").room(.5)`} /> |
|
||||
| pan | <MiniRepl client:visible tune={`s("bd rim bd cp").pan("0 1")`} /> |
|
||||
| speed | <MiniRepl client:visible tune={`s("bd rim bd cp").speed("<1 2 -1 -2>")`} /> |
|
||||
| range | <MiniRepl client:visible tune={`s("hh*32").lpf(saw.range(200,4000))`} /> |
|
||||
|
||||
## Pattern Effects
|
||||
|
||||
| name | description | example |
|
||||
| ---- | ----------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
|
||||
| fast | speed up | <MiniRepl client:visible tune={`sound("bd sd").fast(2)`} /> |
|
||||
| slow | slow down | <MiniRepl client:visible tune={`sound("bd sd").slow(2)`} /> |
|
||||
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").cpm(45)`} /> |
|
||||
| fast | speed up | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").fast(2)`} /> |
|
||||
| slow | slow down | <MiniRepl client:visible tune={`sound("bd sd [~ bd] sd").slow(2)`} /> |
|
||||
| rev | reverse | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
|
||||
| jux | split left/right, modify right | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
|
||||
| add | add numbers / notes | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
|
||||
|
||||
@@ -19,7 +19,7 @@ export function Header({ context }) {
|
||||
isDirty,
|
||||
activeCode,
|
||||
handleTogglePlay,
|
||||
handleUpdate,
|
||||
handleEvaluate,
|
||||
handleShuffle,
|
||||
handleShare,
|
||||
} = context;
|
||||
@@ -85,7 +85,7 @@ export function Header({ context }) {
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleUpdate()}
|
||||
onClick={handleEvaluate}
|
||||
title="update"
|
||||
className={cx(
|
||||
'flex items-center space-x-1',
|
||||
|
||||
@@ -12,14 +12,15 @@ import { defaultAudioDeviceName } from '../settings.mjs';
|
||||
import { getAudioDevices, setAudioDevice } from './util.mjs';
|
||||
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { settingsMap, useSettings } from '../settings.mjs';
|
||||
import {
|
||||
initUserCode,
|
||||
setActivePattern,
|
||||
setLatestCode,
|
||||
settingsMap,
|
||||
updateUserCode,
|
||||
useSettings,
|
||||
} from '../settings.mjs';
|
||||
createPatternID,
|
||||
userPattern,
|
||||
getViewingPatternData,
|
||||
setViewingPatternData,
|
||||
} from '../user_pattern_utils.mjs';
|
||||
import { Header } from './Header';
|
||||
import Loader from './Loader';
|
||||
import { Panel } from './panel/Panel';
|
||||
@@ -29,8 +30,6 @@ import { getRandomTune, initCode, loadModules, shareCode, ReplContext } from './
|
||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||
import './Repl.css';
|
||||
|
||||
const { code: randomTune, name } = getRandomTune();
|
||||
|
||||
const { latestCode } = settingsMap.get();
|
||||
|
||||
let modulesLoading, presets, drawContext, clearCanvas, isIframe;
|
||||
@@ -46,7 +45,6 @@ if (typeof window !== 'undefined') {
|
||||
export function Repl({ embedded = false }) {
|
||||
const isEmbedded = embedded || isIframe;
|
||||
const { panelPosition, isZen } = useSettings();
|
||||
|
||||
const init = useCallback(() => {
|
||||
const drawTime = [-2, 2];
|
||||
const drawContext = getDrawContext();
|
||||
@@ -71,32 +69,48 @@ export function Repl({ embedded = false }) {
|
||||
onUpdateState: (state) => {
|
||||
setReplState({ ...state });
|
||||
},
|
||||
afterEval: ({ code }) => {
|
||||
updateUserCode(code);
|
||||
// setPending(false);
|
||||
afterEval: (all) => {
|
||||
const { code } = all;
|
||||
setLatestCode(code);
|
||||
window.location.hash = '#' + code2hash(code);
|
||||
const viewingPatternData = getViewingPatternData();
|
||||
const data = { ...viewingPatternData, code };
|
||||
let id = data.id;
|
||||
const isExamplePattern = viewingPatternData.collection !== userPattern.collection;
|
||||
|
||||
if (isExamplePattern) {
|
||||
const codeHasChanged = code !== viewingPatternData.code;
|
||||
if (codeHasChanged) {
|
||||
// fork example
|
||||
const newPattern = userPattern.duplicate(data);
|
||||
id = newPattern.id;
|
||||
setViewingPatternData(newPattern.data);
|
||||
}
|
||||
} else {
|
||||
id = userPattern.isValidID(id) ? id : createPatternID();
|
||||
setViewingPatternData(userPattern.update(id, data).data);
|
||||
}
|
||||
setActivePattern(id);
|
||||
},
|
||||
bgFill: false,
|
||||
});
|
||||
|
||||
// init settings
|
||||
|
||||
initCode().then((decoded) => {
|
||||
initCode().then(async (decoded) => {
|
||||
let msg;
|
||||
if (decoded) {
|
||||
editor.setCode(decoded);
|
||||
initUserCode(decoded);
|
||||
msg = `I have loaded the code from the URL.`;
|
||||
} else if (latestCode) {
|
||||
editor.setCode(latestCode);
|
||||
msg = `Your last session has been loaded!`;
|
||||
} else {
|
||||
const { code: randomTune, name } = await getRandomTune();
|
||||
editor.setCode(randomTune);
|
||||
msg = `A random code snippet named "${name}" has been loaded!`;
|
||||
}
|
||||
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
|
||||
// setPending(false);
|
||||
});
|
||||
|
||||
editorRef.current = editor;
|
||||
@@ -138,29 +152,37 @@ export function Repl({ embedded = false }) {
|
||||
// UI Actions
|
||||
//
|
||||
|
||||
const handleTogglePlay = async () => editorRef.current?.toggle();
|
||||
const handleUpdate = async (newCode, reset = false) => {
|
||||
if (reset) {
|
||||
clearCanvas();
|
||||
resetLoadedSounds();
|
||||
editorRef.current.repl.setCps(1);
|
||||
await prebake(); // declare default samples
|
||||
}
|
||||
if (newCode) {
|
||||
editorRef.current.setCode(newCode);
|
||||
editorRef.current.repl.evaluate(newCode);
|
||||
} else if (isDirty) {
|
||||
editorRef.current.evaluate();
|
||||
}
|
||||
const handleTogglePlay = async () => {
|
||||
editorRef.current?.toggle();
|
||||
};
|
||||
const handleShuffle = async () => {
|
||||
// window.postMessage('strudel-shuffle');
|
||||
const { code, name } = getRandomTune();
|
||||
logger(`[repl] ✨ loading random tune "${name}"`);
|
||||
setActivePattern(name);
|
||||
|
||||
const resetEditor = async () => {
|
||||
clearCanvas();
|
||||
resetLoadedSounds();
|
||||
editorRef.current.repl.setCps(0.5);
|
||||
await prebake(); // declare default samples
|
||||
};
|
||||
|
||||
const handleUpdate = async (patternData, reset = false) => {
|
||||
setViewingPatternData(patternData);
|
||||
editorRef.current.setCode(patternData.code);
|
||||
if (reset) {
|
||||
await resetEditor();
|
||||
handleEvaluate();
|
||||
}
|
||||
};
|
||||
|
||||
const handleEvaluate = () => {
|
||||
editorRef.current.evaluate();
|
||||
};
|
||||
const handleShuffle = async () => {
|
||||
const patternData = await getRandomTune();
|
||||
const code = patternData.code;
|
||||
logger(`[repl] ✨ loading random tune "${patternData.id}"`);
|
||||
setActivePattern(patternData.id);
|
||||
setViewingPatternData(patternData);
|
||||
clearCanvas();
|
||||
resetLoadedSounds();
|
||||
editorRef.current.repl.setCps(1);
|
||||
await prebake(); // declare default samples
|
||||
editorRef.current.setCode(code);
|
||||
editorRef.current.repl.evaluate(code);
|
||||
@@ -177,6 +199,7 @@ export function Repl({ embedded = false }) {
|
||||
handleUpdate,
|
||||
handleShuffle,
|
||||
handleShare,
|
||||
handleEvaluate,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,178 +1,190 @@
|
||||
import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
$featuredPatterns,
|
||||
$publicPatterns,
|
||||
clearUserPatterns,
|
||||
deleteActivePattern,
|
||||
duplicateActivePattern,
|
||||
exportPatterns,
|
||||
getUserPattern,
|
||||
importPatterns,
|
||||
newUserPattern,
|
||||
renameActivePattern,
|
||||
setActivePattern,
|
||||
patternFilterName,
|
||||
useActivePattern,
|
||||
useSettings,
|
||||
} from '../../settings.mjs';
|
||||
import * as tunes from '../tunes.mjs';
|
||||
import { useStore } from '@nanostores/react';
|
||||
useViewingPatternData,
|
||||
userPattern,
|
||||
} from '../../user_pattern_utils.mjs';
|
||||
import { useMemo } from 'react';
|
||||
import { getMetadata } from '../../metadata_parser';
|
||||
import { useExamplePatterns } from '../useExamplePatterns';
|
||||
import { parseJSON } from '../util.mjs';
|
||||
import { ButtonGroup } from './Forms.jsx';
|
||||
import { settingsMap, useSettings } from '../../settings.mjs';
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
export function PatternsTab({ context }) {
|
||||
const { userPatterns } = useSettings();
|
||||
const activePattern = useActivePattern();
|
||||
const featuredPatterns = useStore($featuredPatterns);
|
||||
const publicPatterns = useStore($publicPatterns);
|
||||
const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]);
|
||||
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
||||
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
||||
|
||||
let title = meta.title;
|
||||
if (title == null) {
|
||||
const date = new Date(pattern.created_at);
|
||||
if (!isNaN(date)) {
|
||||
title = date.toLocaleDateString();
|
||||
}
|
||||
}
|
||||
if (title == null) {
|
||||
title = pattern.hash;
|
||||
}
|
||||
if (title == null) {
|
||||
title = 'unnamed';
|
||||
}
|
||||
return <>{`${pattern.id}: ${title} by ${Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}`}</>;
|
||||
}
|
||||
|
||||
function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
|
||||
return (
|
||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-4 pb-4">
|
||||
<section>
|
||||
{activePattern && (
|
||||
<div className="flex items-center mb-2 space-x-2 overflow-auto">
|
||||
<h1 className="text-xl">{activePattern}</h1>
|
||||
<div className="space-x-4 flex w-min">
|
||||
{!isExample && (
|
||||
<button className="hover:opacity-50" onClick={() => renameActivePattern()} title="Rename">
|
||||
<PencilSquareIcon className="w-5 h-5" />
|
||||
{/* <PencilIcon className="w-5 h-5" /> */}
|
||||
</button>
|
||||
)}
|
||||
<button className="hover:opacity-50" onClick={() => duplicateActivePattern()} title="Duplicate">
|
||||
<DocumentDuplicateIcon className="w-5 h-5" />
|
||||
</button>
|
||||
{!isExample && (
|
||||
<button className="hover:opacity-50" onClick={() => deleteActivePattern()} title="Delete">
|
||||
<TrashIcon className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="font-mono text-sm">
|
||||
{Object.entries(userPatterns).map(([key, up]) => (
|
||||
<a
|
||||
key={key}
|
||||
className={classNames(
|
||||
'mr-4 hover:opacity-50 cursor-pointer inline-block',
|
||||
key === activePattern ? 'outline outline-1' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
const { code } = up;
|
||||
setActivePattern(key);
|
||||
context.handleUpdate(code, true);
|
||||
}}
|
||||
>
|
||||
{key}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div className="pr-4 space-x-4 border-b border-foreground mb-2 h-8 flex overflow-auto max-w-full items-center">
|
||||
<button
|
||||
className="hover:opacity-50"
|
||||
onClick={() => {
|
||||
const name = newUserPattern();
|
||||
const { code } = getUserPattern(name);
|
||||
context.handleUpdate(code, true);
|
||||
}}
|
||||
>
|
||||
new
|
||||
</button>
|
||||
<button className="hover:opacity-50" onClick={() => clearUserPatterns()}>
|
||||
clear
|
||||
</button>
|
||||
<label className="hover:opacity-50 cursor-pointer">
|
||||
<input
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
multiple
|
||||
accept="text/plain,application/json"
|
||||
onChange={(e) => importPatterns(e.target.files)}
|
||||
<a
|
||||
className={classNames(
|
||||
'mr-4 hover:opacity-50 cursor-pointer block',
|
||||
showOutline && 'outline outline-1',
|
||||
showHiglight && 'bg-selection',
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<PatternLabel pattern={pattern} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function PatternButtons({ patterns, activePattern, onClick, started }) {
|
||||
const viewingPatternStore = useViewingPatternData();
|
||||
const viewingPatternData = parseJSON(viewingPatternStore);
|
||||
const viewingPatternID = viewingPatternData.id;
|
||||
return (
|
||||
<div className="font-mono text-sm">
|
||||
{Object.values(patterns)
|
||||
.reverse()
|
||||
.map((pattern) => {
|
||||
const id = pattern.id;
|
||||
return (
|
||||
<PatternButton
|
||||
pattern={pattern}
|
||||
key={id}
|
||||
showHiglight={id === viewingPatternID}
|
||||
showOutline={id === activePattern && started}
|
||||
onClick={() => onClick(id)}
|
||||
/>
|
||||
import
|
||||
</label>
|
||||
<button className="hover:opacity-50" onClick={() => exportPatterns()}>
|
||||
export
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
{featuredPatterns && (
|
||||
<section>
|
||||
<h2 className="text-xl mb-2">Featured Patterns</h2>
|
||||
<div className="font-mono text-sm">
|
||||
{featuredPatterns.map((pattern) => (
|
||||
<a
|
||||
key={pattern.id}
|
||||
className={classNames(
|
||||
'mr-4 hover:opacity-50 cursor-pointer block',
|
||||
pattern.hash === activePattern ? 'outline outline-1' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
setActivePattern(pattern.hash);
|
||||
context.handleUpdate(pattern.code, true);
|
||||
}}
|
||||
>
|
||||
<PatternLabel pattern={pattern} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{publicPatterns && (
|
||||
<section>
|
||||
<h2 className="text-xl mb-2">Last Creations</h2>
|
||||
<div className="font-mono text-sm">
|
||||
{publicPatterns.map((pattern) => (
|
||||
<a
|
||||
key={'public-' + pattern.id}
|
||||
className={classNames(
|
||||
'mr-4 hover:opacity-50 cursor-pointer block', // inline-block
|
||||
pattern.hash === activePattern ? 'outline outline-1' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
setActivePattern(pattern.hash);
|
||||
context.handleUpdate(pattern.code, true);
|
||||
}}
|
||||
>
|
||||
<PatternLabel pattern={pattern} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<section>
|
||||
<h2 className="text-xl mb-2">Stock Examples</h2>
|
||||
<div className="font-mono text-sm">
|
||||
{Object.entries(tunes).map(([key, tune]) => (
|
||||
<a
|
||||
key={key}
|
||||
className={classNames(
|
||||
'mr-4 hover:opacity-50 cursor-pointer inline-block',
|
||||
key === activePattern ? 'outline outline-1' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
setActivePattern(key);
|
||||
context.handleUpdate(tune, true);
|
||||
}}
|
||||
>
|
||||
{key}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
||||
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
||||
function ActionButton({ children, onClick, label, labelIsHidden }) {
|
||||
return (
|
||||
<>
|
||||
{pattern.id}. {meta.title || pattern.hash} by {Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}
|
||||
</>
|
||||
<button className="hover:opacity-50" onClick={onClick} title={label}>
|
||||
{labelIsHidden !== true && label}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function PatternsTab({ context }) {
|
||||
const activePattern = useActivePattern();
|
||||
const viewingPatternStore = useViewingPatternData();
|
||||
const viewingPatternData = parseJSON(viewingPatternStore);
|
||||
|
||||
const { userPatterns, patternFilter } = useSettings();
|
||||
|
||||
const examplePatterns = useExamplePatterns();
|
||||
const collections = examplePatterns.collections;
|
||||
|
||||
const updateCodeWindow = (patternData, reset = false) => {
|
||||
context.handleUpdate(patternData, reset);
|
||||
};
|
||||
const viewingPatternID = viewingPatternData?.id;
|
||||
|
||||
const autoResetPatternOnChange = !window.parent?.location.pathname.includes('oodles');
|
||||
|
||||
return (
|
||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-2 pb-4 flex flex-col overflow-hidden max-h-full">
|
||||
<ButtonGroup
|
||||
value={patternFilter}
|
||||
onChange={(value) => settingsMap.setKey('patternFilter', value)}
|
||||
items={patternFilterName}
|
||||
></ButtonGroup>
|
||||
{patternFilter === patternFilterName.user && (
|
||||
<div>
|
||||
<div className="pr-4 space-x-4 border-b border-foreground flex max-w-full overflow-x-auto">
|
||||
<ActionButton
|
||||
label="new"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.createAndAddToDB();
|
||||
updateCodeWindow(data);
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
label="duplicate"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.duplicate(viewingPatternData);
|
||||
updateCodeWindow(data);
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
label="delete"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.delete(viewingPatternID);
|
||||
updateCodeWindow({ ...data, collection: userPattern.collection });
|
||||
}}
|
||||
/>
|
||||
<label className="hover:opacity-50 cursor-pointer">
|
||||
<input
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
multiple
|
||||
accept="text/plain,application/json"
|
||||
onChange={(e) => importPatterns(e.target.files)}
|
||||
/>
|
||||
import
|
||||
</label>
|
||||
<ActionButton label="export" onClick={exportPatterns} />
|
||||
|
||||
<ActionButton
|
||||
label="delete-all"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.clearAll();
|
||||
updateCodeWindow(data);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section className="flex overflow-y-scroll max-h-full flex-col">
|
||||
{patternFilter === patternFilterName.user && (
|
||||
<PatternButtons
|
||||
onClick={(id) =>
|
||||
updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, autoResetPatternOnChange)
|
||||
}
|
||||
patterns={userPatterns}
|
||||
started={context.started}
|
||||
activePattern={activePattern}
|
||||
viewingPatternID={viewingPatternID}
|
||||
/>
|
||||
)}
|
||||
{patternFilter !== patternFilterName.user &&
|
||||
Array.from(collections.keys()).map((collection) => {
|
||||
const patterns = collections.get(collection);
|
||||
return (
|
||||
<section key={collection} className="py-2">
|
||||
<h2 className="text-xl mb-2">{collection}</h2>
|
||||
<div className="font-mono text-sm">
|
||||
<PatternButtons
|
||||
onClick={(id) => updateCodeWindow({ ...patterns[id], collection }, autoResetPatternOnChange)}
|
||||
started={context.started}
|
||||
patterns={patterns}
|
||||
activePattern={activePattern}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ stack(
|
||||
)
|
||||
.fast(2/3)
|
||||
.pianoroll()`;
|
||||
|
||||
/*
|
||||
export const bridgeIsOver = `// "Bridge is over"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos, bassline by BDP - The Bridge Is Over
|
||||
@@ -333,7 +333,7 @@ stack(
|
||||
s("mad").slow(2)
|
||||
).cpm(78).slow(4)
|
||||
.pianoroll()
|
||||
`;
|
||||
`; */
|
||||
|
||||
export const goodTimes = `// "Good times"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
@@ -709,6 +709,8 @@ export const dinofunk = `// "Dinofunk"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
setcps(1)
|
||||
|
||||
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
|
||||
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
|
||||
setVoicingRange('lefthand', ['c3','a4'])
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { $featuredPatterns, $publicPatterns, collectionName } from '../user_pattern_utils.mjs';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useMemo } from 'react';
|
||||
import * as tunes from '../repl/tunes.mjs';
|
||||
|
||||
export const stockPatterns = Object.fromEntries(
|
||||
Object.entries(tunes).map(([key, code], i) => [i, { id: i, code, collection: 'Stock Examples' }]),
|
||||
);
|
||||
|
||||
export const useExamplePatterns = () => {
|
||||
const featuredPatterns = useStore($featuredPatterns);
|
||||
const publicPatterns = useStore($publicPatterns);
|
||||
const collections = useMemo(() => {
|
||||
const pats = new Map();
|
||||
pats.set(collectionName.featured, featuredPatterns);
|
||||
pats.set(collectionName.public, publicPatterns);
|
||||
// pats.set(collectionName.stock, stockPatterns);
|
||||
return pats;
|
||||
}, [featuredPatterns, publicPatterns]);
|
||||
|
||||
const patterns = useMemo(() => {
|
||||
return Object.assign({}, ...collections.values());
|
||||
}, [collections]);
|
||||
|
||||
return { patterns, collections };
|
||||
};
|
||||
@@ -4,12 +4,12 @@ import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '
|
||||
|
||||
import { isTauri } from '../tauri.mjs';
|
||||
import './Repl.css';
|
||||
import * as tunes from './tunes.mjs';
|
||||
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { writeText } from '@tauri-apps/api/clipboard';
|
||||
import { createContext } from 'react';
|
||||
import { $publicPatterns, $featuredPatterns } from '../settings.mjs';
|
||||
import { $featuredPatterns, loadDBPatterns } from '@src/user_pattern_utils.mjs';
|
||||
|
||||
// Create a single supabase client for interacting with your database
|
||||
export const supabase = createClient(
|
||||
@@ -17,27 +17,9 @@ export const supabase = createClient(
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
|
||||
);
|
||||
|
||||
export function loadPublicPatterns() {
|
||||
return supabase.from('code').select().eq('public', true).limit(20).order('id', { ascending: false });
|
||||
}
|
||||
|
||||
export function loadFeaturedPatterns() {
|
||||
return supabase.from('code').select().eq('featured', true).limit(20).order('id', { ascending: false });
|
||||
}
|
||||
|
||||
async function loadDBPatterns() {
|
||||
try {
|
||||
const { data: publicPatterns } = await loadPublicPatterns();
|
||||
const { data: featuredPatterns } = await loadFeaturedPatterns();
|
||||
$publicPatterns.set(publicPatterns);
|
||||
$featuredPatterns.set(featuredPatterns);
|
||||
} catch (err) {
|
||||
console.error('error loading patterns');
|
||||
}
|
||||
}
|
||||
|
||||
let dbLoaded;
|
||||
if (typeof window !== 'undefined') {
|
||||
loadDBPatterns();
|
||||
dbLoaded = loadDBPatterns();
|
||||
}
|
||||
|
||||
export async function initCode() {
|
||||
@@ -52,7 +34,7 @@ export async function initCode() {
|
||||
return hash2code(codeParam);
|
||||
} else if (hash) {
|
||||
return supabase
|
||||
.from('code')
|
||||
.from('code_v1')
|
||||
.select('code')
|
||||
.eq('hash', hash)
|
||||
.then(({ data, error }) => {
|
||||
@@ -70,11 +52,21 @@ export async function initCode() {
|
||||
}
|
||||
}
|
||||
|
||||
export function getRandomTune() {
|
||||
const allTunes = Object.entries(tunes);
|
||||
export const parseJSON = (json) => {
|
||||
json = json != null && json.length ? json : '{}';
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return '{}';
|
||||
}
|
||||
};
|
||||
|
||||
export async function getRandomTune() {
|
||||
await dbLoaded;
|
||||
const featuredTunes = Object.entries($featuredPatterns.get());
|
||||
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
const [name, code] = randomItem(allTunes);
|
||||
return { name, code };
|
||||
const [_, data] = randomItem(featuredTunes);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function loadModules() {
|
||||
@@ -120,7 +112,7 @@ export async function shareCode(codeToShare) {
|
||||
// generate uuid in the browser
|
||||
const hash = nanoid(12);
|
||||
const shareUrl = window.location.origin + window.location.pathname + '?' + hash;
|
||||
const { error } = await supabase.from('code').insert([{ code: codeToShare, hash, ['public']: isPublic }]);
|
||||
const { error } = await supabase.from('code_v1').insert([{ code: codeToShare, hash, ['public']: isPublic }]);
|
||||
if (!error) {
|
||||
lastShared = codeToShare;
|
||||
// copy shareUrl to clipboard
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { atom } from 'nanostores';
|
||||
import { persistentMap, persistentAtom } from '@nanostores/persistent';
|
||||
import { persistentMap } from '@nanostores/persistent';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { register } from '@strudel/core';
|
||||
import * as tunes from './repl/tunes.mjs';
|
||||
import { logger } from '@strudel/core';
|
||||
|
||||
export let $publicPatterns = atom([]);
|
||||
export let $featuredPatterns = atom([]);
|
||||
|
||||
export const defaultAudioDeviceName = 'System Standard';
|
||||
|
||||
@@ -26,6 +20,7 @@ export const defaultSettings = {
|
||||
latestCode: '',
|
||||
isZen: false,
|
||||
soundsFilter: 'all',
|
||||
patternFilter: 'community',
|
||||
panelPosition: 'right',
|
||||
userPatterns: '{}',
|
||||
audioDeviceName: defaultAudioDeviceName,
|
||||
@@ -33,26 +28,15 @@ export const defaultSettings = {
|
||||
|
||||
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
|
||||
|
||||
// active pattern is separate, because it shouldn't sync state across tabs
|
||||
// reason: https://github.com/tidalcycles/strudel/issues/857
|
||||
const $activePattern = persistentAtom('activePattern', '', { listen: false });
|
||||
export function setActivePattern(key) {
|
||||
$activePattern.set(key);
|
||||
}
|
||||
export function getActivePattern() {
|
||||
return $activePattern.get();
|
||||
}
|
||||
export function useActivePattern() {
|
||||
return useStore($activePattern);
|
||||
}
|
||||
export function initUserCode(code) {
|
||||
const userPatterns = getUserPatterns();
|
||||
const match = Object.entries(userPatterns).find(([_, pat]) => pat.code === code);
|
||||
setActivePattern(match?.[0] || '');
|
||||
}
|
||||
|
||||
export function useSettings() {
|
||||
const state = useStore(settingsMap);
|
||||
|
||||
const userPatterns = JSON.parse(state.userPatterns);
|
||||
Object.keys(userPatterns).forEach((key) => {
|
||||
const data = userPatterns[key];
|
||||
data.id = data.id ?? key;
|
||||
userPatterns[key] = data;
|
||||
});
|
||||
return {
|
||||
...state,
|
||||
isZen: [true, 'true'].includes(state.isZen) ? true : false,
|
||||
@@ -65,13 +49,12 @@ export function useSettings() {
|
||||
isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false,
|
||||
fontSize: Number(state.fontSize),
|
||||
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
|
||||
userPatterns: JSON.parse(state.userPatterns),
|
||||
userPatterns: userPatterns,
|
||||
};
|
||||
}
|
||||
|
||||
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
||||
|
||||
export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
|
||||
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
||||
|
||||
const patternSetting = (key) =>
|
||||
@@ -90,174 +73,3 @@ export const fontFamily = patternSetting('fontFamily');
|
||||
export const fontSize = patternSetting('fontSize');
|
||||
|
||||
export const settingPatterns = { theme, fontFamily, fontSize };
|
||||
|
||||
export function getUserPatterns() {
|
||||
return JSON.parse(settingsMap.get().userPatterns);
|
||||
}
|
||||
function getSetting(key) {
|
||||
return settingsMap.get()[key];
|
||||
}
|
||||
|
||||
export function setUserPatterns(obj) {
|
||||
settingsMap.setKey('userPatterns', JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export function addUserPattern(name, config) {
|
||||
if (typeof config !== 'object') {
|
||||
throw new Error('addUserPattern expected object as second param');
|
||||
}
|
||||
if (!config.code) {
|
||||
throw new Error('addUserPattern expected code as property of second param');
|
||||
}
|
||||
const userPatterns = getUserPatterns();
|
||||
setUserPatterns({ ...userPatterns, [name]: config });
|
||||
}
|
||||
|
||||
export function newUserPattern() {
|
||||
const userPatterns = getUserPatterns();
|
||||
const date = new Date().toISOString().split('T')[0];
|
||||
const todays = Object.entries(userPatterns).filter(([name]) => name.startsWith(date));
|
||||
const num = String(todays.length + 1).padStart(3, '0');
|
||||
const defaultNewPattern = 's("hh")';
|
||||
const name = date + '_' + num;
|
||||
addUserPattern(name, { code: defaultNewPattern });
|
||||
setActivePattern(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
export function clearUserPatterns() {
|
||||
if (!confirm(`This will delete all your patterns. Are you really sure?`)) {
|
||||
return;
|
||||
}
|
||||
setUserPatterns({});
|
||||
}
|
||||
|
||||
export function getNextCloneName(key) {
|
||||
const userPatterns = getUserPatterns();
|
||||
const clones = Object.entries(userPatterns).filter(([name]) => name.startsWith(key));
|
||||
const num = String(clones.length + 1).padStart(3, '0');
|
||||
return key + '_' + num;
|
||||
}
|
||||
|
||||
export function getUserPattern(key) {
|
||||
const userPatterns = getUserPatterns();
|
||||
return userPatterns[key];
|
||||
}
|
||||
|
||||
export function renameActivePattern() {
|
||||
let activePattern = getActivePattern();
|
||||
let userPatterns = getUserPatterns();
|
||||
if (!userPatterns[activePattern]) {
|
||||
alert('Cannot rename examples');
|
||||
return;
|
||||
}
|
||||
const newName = prompt('Enter new name', activePattern);
|
||||
if (newName === null) {
|
||||
// canceled
|
||||
return;
|
||||
}
|
||||
if (userPatterns[newName]) {
|
||||
alert('Name already taken!');
|
||||
return;
|
||||
}
|
||||
userPatterns[newName] = userPatterns[activePattern]; // copy code
|
||||
delete userPatterns[activePattern];
|
||||
setUserPatterns({ ...userPatterns });
|
||||
setActivePattern(newName);
|
||||
}
|
||||
|
||||
export function updateUserCode(code) {
|
||||
const userPatterns = getUserPatterns();
|
||||
let activePattern = getActivePattern();
|
||||
// check if code is that of an example tune
|
||||
const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || [];
|
||||
if (example && (!activePattern || activePattern === example)) {
|
||||
// select example
|
||||
setActivePattern(example);
|
||||
return;
|
||||
}
|
||||
const publicPattern = $publicPatterns.get().find((pat) => pat.code === code);
|
||||
if (publicPattern) {
|
||||
setActivePattern(publicPattern.hash);
|
||||
return;
|
||||
}
|
||||
const featuredPattern = $featuredPatterns.get().find((pat) => pat.code === code);
|
||||
if (featuredPattern) {
|
||||
setActivePattern(featuredPattern.hash);
|
||||
return;
|
||||
}
|
||||
if (!activePattern) {
|
||||
// create new user pattern
|
||||
activePattern = newUserPattern();
|
||||
setActivePattern(activePattern);
|
||||
} else if (
|
||||
(!!tunes[activePattern] && code !== tunes[activePattern]) || // fork example tune?
|
||||
$publicPatterns.get().find((p) => p.hash === activePattern) || // fork public pattern?
|
||||
$featuredPatterns.get().find((p) => p.hash === activePattern) // fork featured pattern?
|
||||
) {
|
||||
// fork example
|
||||
activePattern = getNextCloneName(activePattern);
|
||||
setActivePattern(activePattern);
|
||||
}
|
||||
setUserPatterns({ ...userPatterns, [activePattern]: { code } });
|
||||
}
|
||||
|
||||
export function deleteActivePattern() {
|
||||
let activePattern = getActivePattern();
|
||||
if (!activePattern) {
|
||||
console.warn('cannot delete: no pattern selected');
|
||||
return;
|
||||
}
|
||||
const userPatterns = getUserPatterns();
|
||||
if (!userPatterns[activePattern]) {
|
||||
alert('Cannot delete examples');
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Really delete the selected pattern "${activePattern}"?`)) {
|
||||
return;
|
||||
}
|
||||
setUserPatterns(Object.fromEntries(Object.entries(userPatterns).filter(([key]) => key !== activePattern)));
|
||||
setActivePattern('');
|
||||
}
|
||||
|
||||
export function duplicateActivePattern() {
|
||||
let activePattern = getActivePattern();
|
||||
let latestCode = getSetting('latestCode');
|
||||
if (!activePattern) {
|
||||
console.warn('cannot duplicate: no pattern selected');
|
||||
return;
|
||||
}
|
||||
const userPatterns = getUserPatterns();
|
||||
activePattern = getNextCloneName(activePattern);
|
||||
setUserPatterns({ ...userPatterns, [activePattern]: { code: latestCode } });
|
||||
setActivePattern(activePattern);
|
||||
}
|
||||
|
||||
export async function importPatterns(fileList) {
|
||||
const files = Array.from(fileList);
|
||||
await Promise.all(
|
||||
files.map(async (file, i) => {
|
||||
const content = await file.text();
|
||||
if (file.type === 'application/json') {
|
||||
const userPatterns = getUserPatterns() || {};
|
||||
setUserPatterns({ ...userPatterns, ...JSON.parse(content) });
|
||||
} else if (file.type === 'text/plain') {
|
||||
const name = file.name.replace(/\.[^/.]+$/, '');
|
||||
addUserPattern(name, { code: content });
|
||||
}
|
||||
}),
|
||||
);
|
||||
logger(`import done!`);
|
||||
}
|
||||
|
||||
export async function exportPatterns() {
|
||||
const userPatterns = getUserPatterns() || {};
|
||||
const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' });
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = window.URL.createObjectURL(blob);
|
||||
const date = new Date().toISOString().split('T')[0];
|
||||
downloadLink.download = `strudel_patterns_${date}.json`;
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import { atom } from 'nanostores';
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { logger } from '@strudel/core';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { settingsMap } from './settings.mjs';
|
||||
import { parseJSON, supabase } from './repl/util.mjs';
|
||||
|
||||
export let $publicPatterns = atom([]);
|
||||
export let $featuredPatterns = atom([]);
|
||||
|
||||
export const collectionName = {
|
||||
user: 'user',
|
||||
public: 'Last Creations',
|
||||
stock: 'Stock Examples',
|
||||
featured: 'Featured',
|
||||
};
|
||||
|
||||
export const patternFilterName = {
|
||||
community: 'community',
|
||||
user: 'user',
|
||||
};
|
||||
|
||||
export let $viewingPatternData = persistentAtom(
|
||||
'viewingPatternData',
|
||||
{
|
||||
id: '',
|
||||
code: '',
|
||||
collection: collectionName.user,
|
||||
created_at: Date.now(),
|
||||
},
|
||||
{ listen: false },
|
||||
);
|
||||
|
||||
export const getViewingPatternData = () => {
|
||||
return parseJSON($viewingPatternData.get());
|
||||
};
|
||||
export const useViewingPatternData = () => {
|
||||
return useStore($viewingPatternData);
|
||||
};
|
||||
|
||||
export const setViewingPatternData = (data) => {
|
||||
$viewingPatternData.set(JSON.stringify(data));
|
||||
};
|
||||
|
||||
export function loadPublicPatterns() {
|
||||
return supabase.from('code_v1').select().eq('public', true).limit(20).order('id', { ascending: false });
|
||||
}
|
||||
|
||||
export function loadFeaturedPatterns() {
|
||||
return supabase.from('code_v1').select().eq('featured', true).limit(20).order('id', { ascending: false });
|
||||
}
|
||||
|
||||
export async function loadDBPatterns() {
|
||||
try {
|
||||
const { data: publicPatterns } = await loadPublicPatterns();
|
||||
const { data: featuredPatterns } = await loadFeaturedPatterns();
|
||||
const featured = {};
|
||||
const pub = {};
|
||||
|
||||
publicPatterns?.forEach((data, key) => (pub[data.id ?? key] = data));
|
||||
featuredPatterns?.forEach((data, key) => (featured[data.id ?? key] = data));
|
||||
$publicPatterns.set(pub);
|
||||
$featuredPatterns.set(featured);
|
||||
} catch (err) {
|
||||
console.error('error loading patterns', err);
|
||||
}
|
||||
}
|
||||
|
||||
// reason: https://github.com/tidalcycles/strudel/issues/857
|
||||
const $activePattern = persistentAtom('activePattern', '', { listen: false });
|
||||
|
||||
export function setActivePattern(key) {
|
||||
$activePattern.set(key);
|
||||
}
|
||||
export function getActivePattern() {
|
||||
return $activePattern.get();
|
||||
}
|
||||
export function useActivePattern() {
|
||||
return useStore($activePattern);
|
||||
}
|
||||
|
||||
export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
|
||||
|
||||
const defaultCode = '';
|
||||
export const userPattern = {
|
||||
collection: collectionName.user,
|
||||
getAll() {
|
||||
const patterns = parseJSON(settingsMap.get().userPatterns);
|
||||
return patterns ?? {};
|
||||
},
|
||||
getPatternData(id) {
|
||||
const userPatterns = this.getAll();
|
||||
return userPatterns[id];
|
||||
},
|
||||
exists(id) {
|
||||
return this.getPatternData(id) != null;
|
||||
},
|
||||
isValidID(id) {
|
||||
return id != null && id.length > 0;
|
||||
},
|
||||
|
||||
create() {
|
||||
const newID = createPatternID();
|
||||
const code = defaultCode;
|
||||
const data = { code, created_at: Date.now(), id: newID, collection: this.collection };
|
||||
return { id: newID, data };
|
||||
},
|
||||
createAndAddToDB() {
|
||||
const newPattern = this.create();
|
||||
return this.update(newPattern.id, newPattern.data);
|
||||
},
|
||||
|
||||
update(id, data) {
|
||||
const userPatterns = this.getAll();
|
||||
data = { ...data, id, collection: this.collection };
|
||||
setUserPatterns({ ...userPatterns, [id]: data });
|
||||
return { id, data };
|
||||
},
|
||||
duplicate(data) {
|
||||
const newPattern = this.create();
|
||||
return this.update(newPattern.id, { ...newPattern.data, code: data.code });
|
||||
},
|
||||
clearAll() {
|
||||
if (!confirm(`This will delete all your patterns. Are you really sure?`)) {
|
||||
return;
|
||||
}
|
||||
const viewingPatternData = getViewingPatternData();
|
||||
setUserPatterns({});
|
||||
|
||||
if (viewingPatternData.collection !== this.collection) {
|
||||
return { id: viewingPatternData.id, data: viewingPatternData };
|
||||
}
|
||||
setActivePattern(null);
|
||||
return this.create();
|
||||
},
|
||||
delete(id) {
|
||||
const userPatterns = this.getAll();
|
||||
delete userPatterns[id];
|
||||
if (getActivePattern() === id) {
|
||||
setActivePattern(null);
|
||||
}
|
||||
setUserPatterns(userPatterns);
|
||||
const viewingPatternData = getViewingPatternData();
|
||||
const viewingID = viewingPatternData?.id;
|
||||
if (viewingID === id) {
|
||||
return { id: null, data: { code: defaultCode } };
|
||||
}
|
||||
return { id: viewingID, data: userPatterns[viewingID] };
|
||||
},
|
||||
};
|
||||
|
||||
function setUserPatterns(obj) {
|
||||
return settingsMap.setKey('userPatterns', JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export const createPatternID = () => {
|
||||
return nanoid(12);
|
||||
};
|
||||
|
||||
export async function importPatterns(fileList) {
|
||||
const files = Array.from(fileList);
|
||||
await Promise.all(
|
||||
files.map(async (file, i) => {
|
||||
const content = await file.text();
|
||||
if (file.type === 'application/json') {
|
||||
const userPatterns = userPattern.getAll();
|
||||
setUserPatterns({ ...userPatterns, ...parseJSON(content) });
|
||||
} else if (file.type === 'text/plain') {
|
||||
const id = file.name.replace(/\.[^/.]+$/, '');
|
||||
userPattern.update(id, { code: content });
|
||||
}
|
||||
}),
|
||||
);
|
||||
logger(`import done!`);
|
||||
}
|
||||
|
||||
export async function exportPatterns() {
|
||||
const userPatterns = userPattern.getAll();
|
||||
const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' });
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = window.URL.createObjectURL(blob);
|
||||
const date = new Date().toISOString().split('T')[0];
|
||||
downloadLink.download = `strudel_patterns_${date}.json`;
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
}
|
||||