├── readme.md ├── .gitignore ├── allscripts(deprecated).js └── prebake.strudel /readme.md: -------------------------------------------------------------------------------- 1 | to use these custom functions, load them in the strudel editor, or upload the .strudel file into the strudel settings prebake section. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | **/.DS_Store 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Optional REPL history 61 | .node_repl_history 62 | 63 | # Output of 'npm pack' 64 | *.tgz 65 | 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | 69 | # dotenv environment variable files 70 | .env 71 | .env.* 72 | !.env.example 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | .parcel-cache 77 | 78 | # Next.js build output 79 | .next 80 | out 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and not Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # vuepress v2.x temp and cache directory 96 | .temp 97 | .cache 98 | 99 | # Sveltekit cache directory 100 | .svelte-kit/ 101 | 102 | # vitepress build output 103 | **/.vitepress/dist 104 | 105 | # vitepress cache directory 106 | **/.vitepress/cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # Firebase cache directory 121 | .firebase/ 122 | 123 | # TernJS port file 124 | .tern-port 125 | 126 | # Stores VSCode versions used for testing VSCode extensions 127 | .vscode-test 128 | 129 | # yarn v3 130 | .pnp.* 131 | .yarn/* 132 | !.yarn/patches 133 | !.yarn/plugins 134 | !.yarn/releases 135 | !.yarn/sdks 136 | !.yarn/versions 137 | 138 | # Vite logs files 139 | vite.config.js.timestamp-* 140 | vite.config.ts.timestamp-* 141 | -------------------------------------------------------------------------------- /allscripts(deprecated).js: -------------------------------------------------------------------------------- 1 | register('o', (orbit, pat) => pat.orbit(orbit)) 2 | samples('http://localhost:5432') 3 | setCpm(140 / 4) 4 | 5 | setGainCurve(x => Math.pow(x, 2)) 6 | 7 | // setGainCurve(x => x**2) 8 | function blockArrange(patArr, modifiers = []) { 9 | return stack( 10 | ...patArr.map(([pat, maskPat]) => { 11 | pat = [pat].flat() 12 | 13 | return maskPat.fmap(m => { 14 | return stack(...pat.map(p => { 15 | 16 | if (m == 0) { 17 | return 18 | } 19 | const ms = m.toString() 20 | let newPat = p 21 | 22 | if (ms.includes('R')) { 23 | newPat = newPat.restart(1) 24 | } 25 | if (ms.includes('B')) { 26 | newPat = newPat.rev().speed(-1) 27 | } 28 | modifiers.forEach(([mod, callback]) => { 29 | if (mod(ms)) { 30 | newPat = callback(newPat) 31 | } 32 | }) 33 | return newPat 34 | }).filter(Boolean)) 35 | }).innerJoin() 36 | }).flat() 37 | ) 38 | } 39 | 40 | // example: 41 | // $: blockArrange( 42 | // [ 43 | // [[bd], ""], 44 | // [[bass], "<0 0 F F F S F F F B>"], 45 | // [[hat], "<0 F F F F F F F F F>"], 46 | // ], 47 | // //ADD CUSTOM BINDINGS 48 | // [[(m) => m.includes('S') , (x) => x.stretch(1)]] 49 | // )._scope() 50 | 51 | // fill in gaps between events 52 | register('fill', function (pat) { 53 | return new Pattern(function (state) { 54 | const lookbothways = 1; 55 | // Expand the query window 56 | const haps = pat.query(state.withSpan(span => new TimeSpan(span.begin.sub(lookbothways), span.end.add(lookbothways)))); 57 | const onsets = haps.map(hap => hap.whole.begin) 58 | // sort fractions 59 | .sort((a, b) => a.compare(b)) 60 | // make unique 61 | .filter((x, i, arr) => i == (arr.length - 1) || x.ne(arr[i + 1])); 62 | const newHaps = []; 63 | for (const hap of haps) { 64 | // Ingore if the part starts after the original query 65 | if (hap.part.begin.gte(state.span.end)) { 66 | continue; 67 | } 68 | 69 | // Find the next onset, to use as an offset 70 | const next = onsets.find(onset => onset.gte(hap.whole.end)); 71 | 72 | // Ignore if the part ended before the original query, and hasn't expanded inside 73 | if (next.lte(state.span.begin)) { 74 | continue; 75 | } 76 | 77 | const whole = new TimeSpan(hap.whole.begin, next); 78 | // Constrain part to original query 79 | const part = new TimeSpan(hap.part.begin.max(state.span.begin), next.min(state.span.end)); 80 | newHaps.push(new Hap(whole, part, hap.value, hap.context, hap.stateful)); 81 | } 82 | return newHaps; 83 | }); 84 | }); 85 | 86 | register('trancegate', (density, seed, length, x) => { 87 | return x.struct(rand.mul(density).round().seg(16).rib(seed, length)).fill().clip(.7) 88 | }) 89 | 90 | // quantize notes to given values: pat.grab("e:f#:c") 91 | register('grab', function (scale, pat) { 92 | // Supports ':' list syntax in mininotation 93 | scale = (Array.isArray(scale) ? scale.flat() : [scale]).flatMap((val) => 94 | typeof val === 'number' ? val : noteToMidi(val) - 48 95 | ); 96 | 97 | return pat.withHap((hap) => { 98 | const isObject = typeof hap.value === 'object'; 99 | let note = isObject ? hap.value.n : hap.value; 100 | if (typeof note === 'number') { 101 | note = note; 102 | } 103 | if (typeof note === 'string') { 104 | note = noteToMidi(note); 105 | } 106 | 107 | if (isObject) { 108 | delete hap.value.n; // remove n so it won't cause trouble 109 | } 110 | const octave = (note / 12) >> 0; 111 | const transpose = octave * 12; 112 | 113 | const goal = note - transpose; 114 | note = 115 | scale.reduce((prev, curr) => { 116 | return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev; 117 | }) + transpose; 118 | 119 | return hap.withValue(() => (isObject ? { ...hap.value, note } : note)); 120 | }); 121 | }); 122 | 123 | setDefault('gain', 1) 124 | 125 | // multi orbit pan for quad setups etc. 126 | // ex: s("bd!4").mpan("3:4", slider(0.761)) 127 | register('mpan', (orbits, amount, pat) => { 128 | const index = Math.round(amount * (orbits.length - 1)) 129 | const orbit = orbits[index] 130 | const pamt = (amount * orbits.length) % 1 131 | return pat.orbit(orbit).pan(pamt) 132 | }) 133 | 134 | // lpf between 0 and 1 135 | register('rlpf', (x, pat) => { return pat.lpf(pure(x).mul(12).pow(4)) }) 136 | 137 | //hpf between 0 and 1 138 | register('rhpf', (x, pat) => { return pat.hpf(pure(x).mul(12).pow(4)) }) 139 | 140 | //@title velocity structure @by Switch Angel 141 | register('vstruct', (ipat,pat) => { 142 | return ipat.outerBind(vel => { 143 | return pat.keepif.out(Math.ceil(vel)).velocity(vel) 144 | }) 145 | },false) 146 | 147 | // sets my vocoder CC key to the specified value, probably only useful for me 148 | register('voc', (key, x) => { 149 | let value; 150 | 151 | switch (key) { 152 | case 'f#': value = 0; break; 153 | case 'g': value = 0.1; break; 154 | case 'g#': value = 0.2; break; 155 | case 'a': value = 0.3; break; 156 | case 'a#': value = 0.4; break; 157 | case 'b': value = 0.45; break; 158 | case 'c': value = 0.5; break; 159 | case 'c#': value = 0.6; break; 160 | case 'd': value = 0.7; break; 161 | case 'd#': value = 0.8; break; 162 | case 'e': value = 0.9; break; 163 | case 'f': value = 1; break; 164 | default: value = 0.5; // fallback value if key is not recognized 165 | } 166 | 167 | return x.ccn(74).ccv(value).midi('tout'); 168 | }); 169 | // tb303 style filter envelope control between 0 & 1 values for useful range 170 | register('acidenv', (x, pat) => pat.rlpf(.25).lpenv(x * 9).lps(.2).lpd(.15)) 171 | 172 | 173 | /** SOUNDS */ 174 | 175 | register('acid', (pat) => { 176 | return pat.s('supersaw') 177 | .detune(.5) 178 | .unison(1) 179 | .lpf(100) 180 | .lpsustain(0.2).lpd(.2).lpenv(2) 181 | .lpq(12) 182 | }) 183 | 184 | 185 | 186 | 187 | $: s("white").clip(0).gain(0) 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /prebake.strudel: -------------------------------------------------------------------------------- 1 | 2 | // tb303 style filter envelope control between 0 & 1 values for useful range 3 | register('acidenv', (x, pat) => pat.lpf(100) 4 | .lpenv(x * 9).lps(.2).lpd(.12).lpq(2) 5 | ) 6 | 7 | setGainCurve(x => Math.pow(x, 2)) 8 | 9 | 10 | //tracker style arrangement 11 | window.track = function(...input) { 12 | const patterns = input.shift() 13 | let mods = Array.isArray(input.at(-1)) ? input.pop() : undefined 14 | if (input.length % 2 !== 0) { 15 | throw new Error('Arrange needs a length paramter for each pattern (length, pattern, length, pattern)'); 16 | } 17 | let sects = []; 18 | let total = 0; 19 | for (let i = 0; i < input.length; i += 2) { 20 | let inp = [input.at(i)].flat() 21 | let cycles = inp.at(0); 22 | let start = inp.at(1) ?? 0 23 | 24 | total += cycles; 25 | 26 | let cpat = input.at(i + 1).innerBind((str, pat) => { 27 | 28 | const pats = [] 29 | str.split(/-+/).forEach((val, index) => { 30 | if (val == false ){ 31 | return 32 | } 33 | let newPat = patterns.at(index) 34 | mods?.forEach(([mod, callback]) => { 35 | if (val != mod) { 36 | return 37 | } 38 | newPat = callback(newPat) 39 | }) 40 | 41 | pats.push(newPat) 42 | }) 43 | return stack(...pats) 44 | }); 45 | 46 | sects.push([cycles, cpat.ribbon(start, cycles).fast(cycles)]); 47 | } 48 | return stepcat(...sects).slow(total); 49 | } 50 | /** 51 | $: track( 52 | [bd, sd, hat, clap, bass], 53 | 2, "--------0------1-------1--------0", 54 | 2, "1-------0------1-------D--------0", 55 | 4, "1-------0------1-------1--------1", 56 | [['D' ,(x) => x.delay(.5)]] //Optional modifiers 57 | ) 58 | */ 59 | 60 | 61 | /** 62 | * Quick Arrange 63 | * Allows to arrange multiple patterns together over multiple cycles. 64 | * Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use. 65 | * 66 | * @return {Pattern} 67 | * @example 68 | * ar( 69 | * 4, "(3,8)", 70 | * 2, "(5,8)" 71 | * ).note() 72 | */ 73 | window.ar = function(...input) { 74 | 75 | if (input.length % 2 !== 0) { 76 | throw new Error('Arrange needs a length paramter for each pattern (length, pattern, length, pattern)'); 77 | } 78 | let sects = []; 79 | let total = 0; 80 | for (let i = 0; i < input.length; i += 2) { 81 | let inp = [input.at(i)].flat() 82 | let cycles = inp.at(0); 83 | let start = inp.at(1) ?? 0 84 | total += cycles; 85 | 86 | let pattern = input.at(i + 1); 87 | sects.push([cycles, pattern.ribbon(start, cycles).fast(cycles)]); 88 | } 89 | return stepcat(...sects).slow(total); 90 | } 91 | 92 | 93 | window.p = stack 94 | 95 | window.blockArrange = function (patArr, modifiers = []) { 96 | return stack( 97 | ...patArr.map(([pat, maskPat]) => { 98 | pat = [pat].flat() 99 | 100 | return maskPat.fmap(m => { 101 | return stack(...pat.map(p => { 102 | 103 | if (m == 0) { 104 | return 105 | } 106 | const ms = m.toString() 107 | let newPat = p 108 | 109 | if (ms.includes('R')) { 110 | newPat = newPat.restart(1) 111 | } 112 | if (ms.includes('B')) { 113 | newPat = newPat.rev().speed(-1) 114 | } 115 | modifiers.forEach(([mod, callback]) => { 116 | if (mod(ms)) { 117 | newPat = callback(newPat) 118 | } 119 | }) 120 | return newPat 121 | }).filter(Boolean)) 122 | }).innerJoin() 123 | }).flat() 124 | ) 125 | } 126 | 127 | // example: 128 | // $: blockArrange( 129 | // [ 130 | // [[bd], ""], 131 | // [[bass], "<0 0 F F F S F F F B>"], 132 | // [[hat], "<0 F F F F F F F F F>"], 133 | // ], 134 | // //ADD CUSTOM BINDINGS 135 | // [[(m) => m.includes('S') , (x) => x.stretch(1)]] 136 | // )._scope() 137 | 138 | // fill in gaps between events 139 | register('fill', function (pat) { 140 | return new Pattern(function (state) { 141 | const lookbothways = 1; 142 | // Expand the query window 143 | const haps = pat.query(state.withSpan(span => new TimeSpan(span.begin.sub(lookbothways), span.end.add(lookbothways)))); 144 | const onsets = haps.map(hap => hap.whole.begin) 145 | // sort fractions 146 | .sort((a, b) => a.compare(b)) 147 | // make unique 148 | .filter((x, i, arr) => i == (arr.length - 1) || x.ne(arr[i + 1])); 149 | const newHaps = []; 150 | for (const hap of haps) { 151 | // Ingore if the part starts after the original query 152 | if (hap.part.begin.gte(state.span.end)) { 153 | continue; 154 | } 155 | 156 | // Find the next onset, to use as an offset 157 | const next = onsets.find(onset => onset.gte(hap.whole.end)); 158 | 159 | // Ignore if the part ended before the original query, and hasn't expanded inside 160 | if (next.lte(state.span.begin)) { 161 | continue; 162 | } 163 | 164 | const whole = new TimeSpan(hap.whole.begin, next); 165 | // Constrain part to original query 166 | const part = new TimeSpan(hap.part.begin.max(state.span.begin), next.min(state.span.end)); 167 | newHaps.push(new Hap(whole, part, hap.value, hap.context, hap.stateful)); 168 | } 169 | return newHaps; 170 | }); 171 | }); 172 | 173 | register('trancegate', (density, seed, length, x) => { 174 | return x.struct(rand.mul(density).round().seg(16).rib(seed, length)).fill().clip(.7) 175 | }) 176 | 177 | register('colorparty', (p, pat) => { 178 | const colors = ['blue', 'yellow', 'violet', 'green', 'orange', 'cyan', 'magenta', 'white'] 179 | return pat.color(colors.at(Math.floor(p * colors.length))) 180 | }) 181 | 182 | // quantize notes to given values: pat.grab("e:f#:c") 183 | register('grab', function (scale, pat) { 184 | // Supports ':' list syntax in mininotation 185 | scale = (Array.isArray(scale) ? scale.flat() : [scale]).flatMap((val) => 186 | typeof val === 'number' ? val : noteToMidi(val) - 48 187 | ); 188 | 189 | return pat.withHap((hap) => { 190 | const isObject = typeof hap.value === 'object'; 191 | let note = isObject ? hap.value.n : hap.value; 192 | if (typeof note === 'number') { 193 | note = note; 194 | } 195 | if (typeof note === 'string') { 196 | note = noteToMidi(note); 197 | } 198 | 199 | if (isObject) { 200 | delete hap.value.n; // remove n so it won't cause trouble 201 | } 202 | const octave = (note / 12) >> 0; 203 | const transpose = octave * 12; 204 | 205 | const goal = note - transpose; 206 | note = 207 | scale.reduce((prev, curr) => { 208 | return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev; 209 | }) + transpose; 210 | 211 | return hap.withValue(() => (isObject ? { ...hap.value, note } : note)); 212 | }); 213 | }); 214 | 215 | setDefault('gain', 1) 216 | 217 | // multi orbit pan for quad setups etc. 218 | // ex: s("bd!4").mpan("3:4", slider(0.761)) 219 | register('mpan', (orbits, amount, pat) => { 220 | const index = Math.round(amount * (orbits.length - 1)) 221 | const orbit = orbits[index] 222 | const pamt = (amount * orbits.length) % 1 223 | return pat.orbit(orbit).pan(pamt) 224 | }) 225 | 226 | // lpf between 0 and 1 227 | register('rlpf', (x, pat) => { return pat.lpf(pure(x).mul(12).pow(4)) }) 228 | 229 | //hpf between 0 and 1 230 | register('rhpf', (x, pat) => { return pat.hpf(pure(x).mul(12).pow(4)) }) 231 | 232 | //@title velocity structure @by Switch Angel 233 | register('vstruct', (ipat,pat) => { 234 | return ipat.outerBind(vel => { 235 | return pat.keepif.out(Math.ceil(vel)).velocity(vel) 236 | }) 237 | },false) 238 | 239 | // sets my Ableton vocoder CC key to the specified value, probably only useful for me 240 | 241 | register('voc', (key, x) => { 242 | let value; 243 | switch (key.toLowerCase()) { 244 | case 'c': value = 0; break; 245 | case 'c#': value = 0.2; break; 246 | case 'd': value = 0.25; break; 247 | case 'd#': value = 0.3; break; 248 | case 'e': value = 0.4; break; 249 | case 'f': value = 0.5; break; 250 | case 'f#': value = 0.6; break; 251 | case 'g': value = 0.7; break; 252 | case 'g#': value = 0.75; break; 253 | case 'a': value = 0.8; break; 254 | case 'a#': value = 0.9; break; 255 | case 'b': value = 1; break; 256 | default: value = 0; // fallback value if key is not recognized 257 | } 258 | 259 | return x.ccn(34).ccv(value).midi('tout'); 260 | }); 261 | 262 | register('fmtime', (start,length, pat) => { 263 | let modu = time.mod(length).add(start) 264 | return pat.fm(modu).fmh(modu) 265 | }) 266 | 267 | window.irando = (ipat) => reify(ipat).fmap(_irand).outerJoin() 268 | 269 | window.randm = (division) => irand(division).div(16) 270 | window.pk = function (...args) { 271 | const control = args.length > 2 ? args.pop() : 0 272 | return pick(args, control) 273 | } 274 | 275 | 276 | register('acid', (pat) => { 277 | return pat.s('supersaw') 278 | .detune(.5) 279 | .unison(1) 280 | .lpf(100) 281 | .lpsustain(0.2).lpd(.2).lpenv(2) 282 | .lpq(12) 283 | }) --------------------------------------------------------------------------------