Merge branch 'main' into claviature-take2

This commit is contained in:
Felix Roos
2024-03-30 16:08:08 +01:00
18 changed files with 362 additions and 130 deletions
+3
View File
@@ -127,3 +127,6 @@ fabric.properties
.idea/caches/build_file_checksums.ser
# END JetBrains -> BEGIN JetBrains
samples/*
!samples/README.md
+1
View File
@@ -25,6 +25,7 @@
"format-check": "prettier --check .",
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
"check": "npm run format-check && npm run lint && npm run test",
"sampler": "cd samples && node ../packages/sampler/sample-server.mjs",
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
},
"repository": {
+5 -1
View File
@@ -27,6 +27,7 @@ import { persistentAtom } from '@nanostores/persistent';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []),
isBracketClosingEnabled: (on) => (on ? closeBrackets() : []),
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
theme,
isAutoCompletionEnabled,
@@ -41,6 +42,7 @@ const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [ke
export const defaultSettings = {
keybindings: 'codemirror',
isBracketMatchingEnabled: false,
isBracketClosingEnabled: true,
isLineNumbersDisplayed: true,
isActiveLineHighlighted: false,
isAutoCompletionEnabled: false,
@@ -76,7 +78,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
widgetPlugin,
// indentOnInput(), // works without. already brought with javascript extension?
// bracketMatching(), // does not do anything
closeBrackets(),
syntaxHighlighting(defaultHighlightStyle),
history(),
EditorView.updateListener.of((v) => onChange(v)),
@@ -309,6 +310,9 @@ export class StrudelMirror {
setLineNumbersDisplayed(enabled) {
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
}
setBracketClosingEnabled(enabled) {
this.reconfigureExtension('isBracketClosingEnabled', enabled);
}
setTheme(theme) {
this.reconfigureExtension('theme', theme);
}
+22 -18
View File
@@ -542,7 +542,7 @@ export class Pattern {
* @noAutocomplete
*/
filterValues(value_test) {
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value)));
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setTactus(this.tactus);
}
/**
@@ -1147,7 +1147,7 @@ Pattern.prototype.factories = {
slowcat,
fastcat,
cat,
timeCat,
timecat,
sequence,
seq,
polymeter,
@@ -1254,14 +1254,14 @@ function _stackWith(func, pats) {
export function stackLeft(...pats) {
return _stackWith(
(tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : timeCat(pat, gap(tactus.sub(pat.tactus))))),
(tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : timecat(pat, gap(tactus.sub(pat.tactus))))),
pats,
);
}
export function stackRight(...pats) {
return _stackWith(
(tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : timeCat(gap(tactus.sub(pat.tactus)), pat))),
(tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : timecat(gap(tactus.sub(pat.tactus)), pat))),
pats,
);
}
@@ -1274,7 +1274,7 @@ export function stackCentre(...pats) {
return pat;
}
const g = gap(tactus.sub(pat.tactus).div(2));
return timeCat(g, pat, g);
return timecat(g, pat, g);
}),
pats,
);
@@ -1364,13 +1364,13 @@ export function cat(...pats) {
* the pattern's 'tactus', generally inferred by the mininotation.
* @return {Pattern}
* @example
* timeCat([3,"e3"],[1, "g3"]).note()
* timecat([3,"e3"],[1, "g3"]).note()
* // the same as "e3@3 g3".note()
* @example
* timeCat("bd sd cp","hh hh").sound()
* timecat("bd sd cp","hh hh").sound()
* // the same as "bd sd cp hh hh".sound()
*/
export function timeCat(...timepats) {
export function timecat(...timepats) {
const findtactus = (x) => (Array.isArray(x) ? x : [x.tactus, x]);
timepats = timepats.map(findtactus);
if (timepats.length == 1) {
@@ -1392,6 +1392,9 @@ export function timeCat(...timepats) {
return result;
}
/** Deprecated alias for `timecat` */
export const timeCat = timecat;
/**
* Allows to arrange multiple patterns together over multiple cycles.
* Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use.
@@ -1406,7 +1409,7 @@ export function timeCat(...timepats) {
export function arrange(...sections) {
const total = sections.reduce((sum, [cycles]) => sum + cycles, 0);
sections = sections.map(([cycles, section]) => [cycles, section.fast(cycles)]);
return timeCat(...sections).slow(total);
return timecat(...sections).slow(total);
}
export function fastcat(...pats) {
@@ -1418,14 +1421,20 @@ export function fastcat(...pats) {
return result;
}
/** See `fastcat` */
export function sequence(...pats) {
return fastcat(...pats);
}
/**
* Concatenates patterns beatwise, similar to `timeCat`, but if an argument is a list, the whole pattern will be repeated for each element in the list.
* Concatenates patterns stepwise, according to their 'tactus'.
* Similar to `timecat`, but if an argument is a list, the whole pattern will be repeated for each element in the list.
*
* @return {Pattern}
* @example
* beatCat(["bd cp", "mt"], "bd").sound()
* stepcat(["bd cp", "mt"], "bd").sound()
*/
export function beatCat(...groups) {
export function stepcat(...groups) {
groups = groups.map((a) => (Array.isArray(a) ? a.map(reify) : [reify(a)]));
const cycles = lcm(...groups.map((x) => Fraction(x.length)));
@@ -1436,16 +1445,11 @@ export function beatCat(...groups) {
}
result = result.filter((x) => x.tactus > 0);
const tactus = result.reduce((a, b) => a.add(b.tactus), Fraction(0));
result = timeCat(...result);
result = timecat(...result);
result.tactus = tactus;
return result;
}
/** See `fastcat` */
export function sequence(...pats) {
return fastcat(...pats);
}
/** Like **cat**, but the items are crammed into one cycle.
* @synonyms fastcat, sequence
* @example
-4
View File
@@ -22,8 +22,4 @@ describe('Value', () => {
expect(valued(mul).ap(3).ap(3).value).toEqual(9);
expect(valued(3).mul(3).value).toEqual(9);
});
it('union bare numbers for numeral props', () => {
expect(n(3).cutoff(500).add(10).firstCycleValues).toEqual([{ n: 13, cutoff: 510 }]);
expect(n(3).cutoff(500).mul(2).firstCycleValues).toEqual([{ n: 6, cutoff: 1000 }]);
});
});
+5 -6
View File
@@ -5,14 +5,13 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { curry } from './util.mjs';
import { logger } from './logger.mjs';
export function unionWithObj(a, b, func) {
if (typeof b?.value === 'number') {
// https://github.com/tidalcycles/strudel/issues/262
const numKeys = Object.keys(a).filter((k) => typeof a[k] === 'number');
const numerals = Object.fromEntries(numKeys.map((k) => [k, b.value]));
b = Object.assign(b, numerals);
delete b.value;
if (b?.value !== undefined && Object.keys(b).length === 1) {
// https://github.com/tidalcycles/strudel/issues/1026
logger(`[warn]: Can't do arithmetic on control pattern.`);
return a;
}
const common = Object.keys(a).filter((k) => Object.keys(b).includes(k));
return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])])));
+22
View File
@@ -0,0 +1,22 @@
# @strudel/sampler
This package allows you to serve your samples on disk to the strudel REPL.
```sh
cd ~/your/samples/
npx @strudel/sampler
```
This will run a server on `http://localhost:5432`.
You can now load the samples via:
```js
samples('http://localhost:5432')
```
## Options
```sh
LOG=1 npx @strudel/sampler # adds logging
PORT=5555 npx @strudel/sampler # changes port
```
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@strudel/sampler",
"version": "0.0.8",
"description": "",
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bin": "./sample-server.mjs",
"type": "module",
"dependencies": {
"cowsay": "^1.6.0"
}
}
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env node
import cowsay from 'cowsay';
import { createReadStream } from 'fs';
import { readdir } from 'fs/promises';
import http from 'http';
import { join } from 'path';
import os from 'os';
// eslint-disable-next-line
const LOG = !!process.env.LOG || false;
console.log(
cowsay.say({
text: 'welcome to @strudel/sampler',
e: 'oO',
T: 'U ',
}),
);
async function getFilesInDirectory(directory) {
let files = [];
const dirents = await readdir(directory, { withFileTypes: true });
for (const dirent of dirents) {
const fullPath = join(directory, dirent.name);
if (dirent.isDirectory()) {
if (dirent.name.startsWith('.')) {
LOG && console.warn(`ignore hidden folder: ${fullPath}`);
continue;
}
try {
const subFiles = (await getFilesInDirectory(fullPath)).filter((f) =>
['wav', 'mp3', 'ogg'].includes(f.split('.').slice(-1)[0].toLowerCase()),
);
files = files.concat(subFiles);
LOG && console.log(`${dirent.name} (${subFiles.length})`);
} catch (err) {
LOG && console.warn(`skipped due to error: ${fullPath}`);
}
} else {
files.push(fullPath);
}
}
return files;
}
async function getBanks(directory) {
// const directory = resolve(__dirname, '.');
let files = await getFilesInDirectory(directory);
let banks = {};
files = files.map((url) => {
const [bank] = url.split('/').slice(-2);
banks[bank] = banks[bank] || [];
url = url.replace(directory, '');
banks[bank].push(url);
return url;
});
banks._base = `http://localhost:5432`;
return { banks, files };
}
// eslint-disable-next-line
const directory = process.cwd();
const server = http.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
const { banks, files } = await getBanks(directory);
if (req.url === '/') {
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify(banks));
}
let subpath = decodeURIComponent(req.url);
if (!files.includes(subpath)) {
res.statusCode = 404;
res.end('File not found');
return;
}
const filePath = join(directory, subpath);
const readStream = createReadStream(filePath);
readStream.on('error', (err) => {
res.statusCode = 500;
res.end('Internal server error');
console.error(err);
});
readStream.pipe(res);
});
// eslint-disable-next-line
const PORT = process.env.PORT || 5432;
const IP_ADDRESS = '0.0.0.0';
let IP;
const networkInterfaces = os.networkInterfaces();
Object.keys(networkInterfaces).forEach((key) => {
networkInterfaces[key].forEach((networkInterface) => {
if (networkInterface.family === 'IPv4' && !networkInterface.internal) {
IP = networkInterface.address;
}
});
});
if (!IP) {
console.error("Unable to determine server's IP address.");
// eslint-disable-next-line
process.exit(1);
}
server.listen(PORT, IP_ADDRESS, () => {
console.log(`@strudel/sampler is now serving audio files from:
${directory}
To use them in the Strudel REPL, run:
samples('http://localhost:${PORT}')
Or on a machine in the same network:
samples('http://${IP}:${PORT}')
`);
});
+3
View File
@@ -194,6 +194,9 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
if (sampleMap.startsWith('github:')) {
sampleMap = githubPath(sampleMap, 'strudel.json');
}
if (sampleMap.startsWith('local:')) {
sampleMap = `http://localhost:5432`;
}
if (sampleMap.startsWith('shabda:')) {
let [_, path] = sampleMap.split('shabda:');
sampleMap = `https://shabda.ndre.gr/${path}.json?strudel=1`;
+96 -39
View File
@@ -361,6 +361,12 @@ importers:
specifier: ^5.0.10
version: 5.0.10
packages/sampler:
dependencies:
cowsay:
specifier: ^1.6.0
version: 1.6.0
packages/serial:
dependencies:
'@strudel/core':
@@ -5417,10 +5423,10 @@ packages:
type-fest: 0.21.3
dev: true
/ansi-regex@2.1.1:
resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
engines: {node: '>=0.10.0'}
dev: true
/ansi-regex@3.0.1:
resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==}
engines: {node: '>=4'}
dev: false
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@@ -6032,7 +6038,6 @@ packages:
/camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
dev: true
/camelcase@7.0.1:
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
@@ -6199,6 +6204,14 @@ packages:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
/cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
dev: false
/cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
dependencies:
@@ -6261,11 +6274,6 @@ packages:
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dev: true
/code-point-at@1.1.0:
resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
engines: {node: '>=0.10.0'}
dev: true
/collapse-white-space@2.1.0:
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
dev: false
@@ -6533,6 +6541,17 @@ packages:
typescript: 5.3.3
dev: true
/cowsay@1.6.0:
resolution: {integrity: sha512-8C4H1jdrgNusTQr3Yu4SCm+ZKsAlDFbpa0KS0Z3im8ueag+9pGOf3CrioruvmeaW/A5oqg9L0ar6qeftAh03jw==}
engines: {node: '>= 4'}
hasBin: true
dependencies:
get-stdin: 8.0.0
string-width: 2.1.1
strip-final-newline: 2.0.0
yargs: 15.4.1
dev: false
/crelt@1.0.5:
resolution: {integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==}
dev: false
@@ -6651,7 +6670,6 @@ packages:
/decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
dev: true
/decode-named-character-reference@1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
@@ -7877,7 +7895,6 @@ packages:
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/get-east-asian-width@1.2.0:
resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==}
@@ -7916,6 +7933,11 @@ packages:
engines: {node: '>=8'}
dev: true
/get-stdin@8.0.0:
resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==}
engines: {node: '>=10'}
dev: false
/get-stream@6.0.0:
resolution: {integrity: sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==}
engines: {node: '>=10'}
@@ -8779,12 +8801,10 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
/is-fullwidth-code-point@1.0.0:
resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
engines: {node: '>=0.10.0'}
dependencies:
number-is-nan: 1.0.1
dev: true
/is-fullwidth-code-point@2.0.0:
resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==}
engines: {node: '>=4'}
dev: false
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
@@ -10893,11 +10913,6 @@ packages:
set-blocking: 2.0.0
dev: true
/number-is-nan@1.0.1:
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
engines: {node: '>=0.10.0'}
dev: true
/nx@17.2.8:
resolution: {integrity: sha512-rM5zXbuXLEuqQqcjVjClyvHwRJwt+NVImR2A6KFNG40Z60HP6X12wAxxeLHF5kXXTDRU0PFhf/yACibrpbPrAw==}
hasBin: true
@@ -12187,13 +12202,16 @@ packages:
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
dev: true
/require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
dev: false
/requirejs-config-file@4.0.0:
resolution: {integrity: sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==}
engines: {node: '>=10.13.0'}
@@ -12517,7 +12535,6 @@ packages:
/set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: true
/set-function-length@1.1.1:
resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
@@ -12890,14 +12907,13 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/string-width@1.0.2:
resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==}
engines: {node: '>=0.10.0'}
/string-width@2.1.1:
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
engines: {node: '>=4'}
dependencies:
code-point-at: 1.1.0
is-fullwidth-code-point: 1.0.0
strip-ansi: 3.0.1
dev: true
is-fullwidth-code-point: 2.0.0
strip-ansi: 4.0.0
dev: false
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -12999,12 +13015,12 @@ packages:
is-regexp: 1.0.0
dev: true
/strip-ansi@3.0.1:
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
engines: {node: '>=0.10.0'}
/strip-ansi@4.0.0:
resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==}
engines: {node: '>=4'}
dependencies:
ansi-regex: 2.1.1
dev: true
ansi-regex: 3.0.1
dev: false
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
@@ -13039,7 +13055,6 @@ packages:
/strip-final-newline@2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
dev: true
/strip-final-newline@3.0.0:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
@@ -14211,6 +14226,10 @@ packages:
is-symbol: 1.0.4
dev: true
/which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
dev: false
/which-pm-runs@1.1.0:
resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
engines: {node: '>=4'}
@@ -14267,7 +14286,7 @@ packages:
/wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
dependencies:
string-width: 1.0.2
string-width: 4.2.3
dev: true
/widest-line@4.0.1:
@@ -14459,6 +14478,15 @@ packages:
worker-timers-worker: 7.0.67
dev: false
/wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -14551,6 +14579,10 @@ packages:
engines: {node: '>=0.4'}
dev: true
/y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: false
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -14567,6 +14599,14 @@ packages:
engines: {node: '>= 14'}
dev: false
/yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
dev: false
/yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
@@ -14576,6 +14616,23 @@ packages:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
/yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
dev: false
/yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
+5
View File
@@ -0,0 +1,5 @@
# samples folder
1. copy any samples to this folder
2. run `npx @strudel/sampler` from this folder
3. add `samples('local:')` to your code
+27 -48
View File
@@ -954,31 +954,6 @@ exports[`runs examples > example "bank" example index 0 1`] = `
]
`;
exports[`runs examples > example "beatCat" example index 0 1`] = `
[
"[ 0/1 → 1/5 | s:bd ]",
"[ 1/5 → 2/5 | s:cp ]",
"[ 2/5 → 3/5 | s:bd ]",
"[ 3/5 → 4/5 | s:mt ]",
"[ 4/5 → 1/1 | s:bd ]",
"[ 1/1 → 6/5 | s:bd ]",
"[ 6/5 → 7/5 | s:cp ]",
"[ 7/5 → 8/5 | s:bd ]",
"[ 8/5 → 9/5 | s:mt ]",
"[ 9/5 → 2/1 | s:bd ]",
"[ 2/1 → 11/5 | s:bd ]",
"[ 11/5 → 12/5 | s:cp ]",
"[ 12/5 → 13/5 | s:bd ]",
"[ 13/5 → 14/5 | s:mt ]",
"[ 14/5 → 3/1 | s:bd ]",
"[ 3/1 → 16/5 | s:bd ]",
"[ 16/5 → 17/5 | s:cp ]",
"[ 17/5 → 18/5 | s:bd ]",
"[ 18/5 → 19/5 | s:mt ]",
"[ 19/5 → 4/1 | s:bd ]",
]
`;
exports[`runs examples > example "begin" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:rave begin:0 ]",
@@ -5672,27 +5647,6 @@ exports[`runs examples > example "rev" example index 0 1`] = `
]
`;
exports[`runs examples > example "reweight" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd ]",
"[ 1/4 → 1/2 | s:sd ]",
"[ 1/2 → 3/4 | s:cp ]",
"[ 3/4 → 1/1 | s:bd ]",
"[ 1/1 → 5/4 | s:sd ]",
"[ 5/4 → 3/2 | s:cp ]",
"[ 3/2 → 7/4 | s:bd ]",
"[ 7/4 → 2/1 | s:sd ]",
"[ 2/1 → 9/4 | s:cp ]",
"[ 9/4 → 5/2 | s:bd ]",
"[ 5/2 → 11/4 | s:sd ]",
"[ 11/4 → 3/1 | s:cp ]",
"[ 3/1 → 13/4 | s:bd ]",
"[ 13/4 → 7/2 | s:sd ]",
"[ 7/2 → 15/4 | s:cp ]",
"[ 15/4 → 4/1 | s:bd ]",
]
`;
exports[`runs examples > example "ribbon" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:d ]",
@@ -7133,6 +7087,31 @@ exports[`runs examples > example "stack" example index 0 2`] = `
]
`;
exports[`runs examples > example "stepcat" example index 0 1`] = `
[
"[ 0/1 → 1/5 | s:bd ]",
"[ 1/5 → 2/5 | s:cp ]",
"[ 2/5 → 3/5 | s:bd ]",
"[ 3/5 → 4/5 | s:mt ]",
"[ 4/5 → 1/1 | s:bd ]",
"[ 1/1 → 6/5 | s:bd ]",
"[ 6/5 → 7/5 | s:cp ]",
"[ 7/5 → 8/5 | s:bd ]",
"[ 8/5 → 9/5 | s:mt ]",
"[ 9/5 → 2/1 | s:bd ]",
"[ 2/1 → 11/5 | s:bd ]",
"[ 11/5 → 12/5 | s:cp ]",
"[ 12/5 → 13/5 | s:bd ]",
"[ 13/5 → 14/5 | s:mt ]",
"[ 14/5 → 3/1 | s:bd ]",
"[ 3/1 → 16/5 | s:bd ]",
"[ 16/5 → 17/5 | s:cp ]",
"[ 17/5 → 18/5 | s:bd ]",
"[ 18/5 → 19/5 | s:mt ]",
"[ 19/5 → 4/1 | s:bd ]",
]
`;
exports[`runs examples > example "striate" example index 0 1`] = `
[
"[ 0/1 → 1/6 | s:numbers n:0 begin:0 end:0.16666666666666666 ]",
@@ -7319,7 +7298,7 @@ exports[`runs examples > example "sustain" example index 0 1`] = `
]
`;
exports[`runs examples > example "timeCat" example index 0 1`] = `
exports[`runs examples > example "timecat" example index 0 1`] = `
[
"[ 0/1 → 3/4 | note:e3 ]",
"[ 3/4 → 1/1 | note:g3 ]",
@@ -7332,7 +7311,7 @@ exports[`runs examples > example "timeCat" example index 0 1`] = `
]
`;
exports[`runs examples > example "timeCat" example index 1 1`] = `
exports[`runs examples > example "timecat" example index 1 1`] = `
[
"[ 0/1 → 1/5 | s:bd ]",
"[ 1/5 → 2/5 | s:sd ]",
+1 -1
View File
@@ -69,7 +69,7 @@ export default defineConfig({
registerType: 'autoUpdate',
injectRegister: 'auto',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,json,wav,mp3,ogg}'],
globPatterns: ['**/*.{js,css,html,ico,png,svg,json,wav,mp3,ogg,ttf,woff2,TTF,otf}'],
runtimeCaching: [
{
urlPattern: ({ url }) =>
+2 -2
View File
@@ -61,12 +61,12 @@ async function getModule(name) {
export function Repl({ embedded = false }) {
const isEmbedded = embedded || isIframe;
const { panelPosition, isZen } = useSettings();
const { panelPosition, isZen, isSyncEnabled } = useSettings();
const init = useCallback(() => {
const drawTime = [-2, 2];
const drawContext = getDrawContext();
const editor = new StrudelMirror({
sync: false,
sync: isSyncEnabled,
defaultOutput: webaudioOutput,
getTime: () => getAudioContext().currentTime,
setInterval,
+17
View File
@@ -81,6 +81,7 @@ export function SettingsTab({ started }) {
const {
theme,
keybindings,
isBracketClosingEnabled,
isBracketMatchingEnabled,
isLineNumbersDisplayed,
isPatternHighlightingEnabled,
@@ -88,6 +89,7 @@ export function SettingsTab({ started }) {
isAutoCompletionEnabled,
isTooltipEnabled,
isFlashEnabled,
isSyncEnabled,
isLineWrappingEnabled,
fontSize,
fontFamily,
@@ -147,6 +149,11 @@ export function SettingsTab({ started }) {
onChange={(cbEvent) => settingsMap.setKey('isBracketMatchingEnabled', cbEvent.target.checked)}
value={isBracketMatchingEnabled}
/>
<Checkbox
label="Auto close brackets"
onChange={(cbEvent) => settingsMap.setKey('isBracketClosingEnabled', cbEvent.target.checked)}
value={isBracketClosingEnabled}
/>
<Checkbox
label="Display line numbers"
onChange={(cbEvent) => settingsMap.setKey('isLineNumbersDisplayed', cbEvent.target.checked)}
@@ -182,6 +189,16 @@ export function SettingsTab({ started }) {
onChange={(cbEvent) => settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)}
value={isFlashEnabled}
/>
<Checkbox
label="Sync across Browser Tabs / Windows"
onChange={(cbEvent) => {
if (confirm('Changing this setting requires the window to reload itself. OK?')) {
settingsMap.setKey('isSyncEnabled', cbEvent.target.checked);
window.location.reload();
}
}}
value={isSyncEnabled}
/>
</FormItem>
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
<FormItem label="Reset Settings">
+2 -2
View File
@@ -361,8 +361,8 @@ export const echoPiano = `// "Echo piano"
// @by Felix Roos
n("<0 2 [4 6](3,4,2) 3*2>").color('salmon')
.off(1/4, x=>x.add(2).color('green'))
.off(1/2, x=>x.add(6).color('steelblue'))
.off(1/4, x=>x.add(n(2)).color('green'))
.off(1/2, x=>x.add(n(6)).color('steelblue'))
.scale('D minor')
.echo(4, 1/8, .5)
.clip(.5)
+15 -9
View File
@@ -8,11 +8,13 @@ export const defaultSettings = {
activeFooter: 'intro',
keybindings: 'codemirror',
isBracketMatchingEnabled: true,
isBracketClosingEnabled: true,
isLineNumbersDisplayed: true,
isActiveLineHighlighted: true,
isAutoCompletionEnabled: false,
isTooltipEnabled: false,
isFlashEnabled: true,
isSyncEnabled: false,
isLineWrappingEnabled: false,
isPatternHighlightingEnabled: true,
theme: 'strudelTheme',
@@ -29,6 +31,8 @@ export const defaultSettings = {
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
export function useSettings() {
const state = useStore(settingsMap);
@@ -40,15 +44,17 @@ export function useSettings() {
});
return {
...state,
isZen: [true, 'true'].includes(state.isZen) ? true : false,
isBracketMatchingEnabled: [true, 'true'].includes(state.isBracketMatchingEnabled) ? true : false,
isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false,
isActiveLineHighlighted: [true, 'true'].includes(state.isActiveLineHighlighted) ? true : false,
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false,
isTooltipEnabled: [true, 'true'].includes(state.isTooltipEnabled) ? true : false,
isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false,
isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false,
isZen: parseBoolean(state.isZen),
isBracketMatchingEnabled: parseBoolean(state.isBracketMatchingEnabled),
isBracketClosingEnabled: parseBoolean(state.isBracketClosingEnabled),
isLineNumbersDisplayed: parseBoolean(state.isLineNumbersDisplayed),
isActiveLineHighlighted: parseBoolean(state.isActiveLineHighlighted),
isAutoCompletionEnabled: parseBoolean(state.isAutoCompletionEnabled),
isPatternHighlightingEnabled: parseBoolean(state.isPatternHighlightingEnabled),
isTooltipEnabled: parseBoolean(state.isTooltipEnabled),
isLineWrappingEnabled: parseBoolean(state.isLineWrappingEnabled),
isFlashEnabled: parseBoolean(state.isFlashEnabled),
isSyncEnabled: parseBoolean(state.isSyncEnabled),
fontSize: Number(state.fontSize),
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
userPatterns: userPatterns,