Compare commits

..

2 Commits

Author SHA1 Message Date
Jade (Rose) Rowland 8722c94bbe fix first pattern muted case 2026-08-01 09:51:53 +01:00
Jade (Rose) Rowland b606bf4692 fix labels 2026-07-30 11:19:06 +01:00
6 changed files with 90 additions and 40 deletions
+1 -11
View File
@@ -52,17 +52,7 @@ Strudel is a project handmade by humans, with thought and nuance.
If you have used LLMs (so called 'AI'), please detail that in the pull request. We are still developing our response to the onslaught of LLM technology, but for practical and legal reasons are currently not accepting wholly LLM-generated code. We are also not accepting PRs that add LLM features to strudel itself.
There are #llm-chat and #llm-share channels on [our discord](https://discord.com/invite/HGEdXmRkzT). Please do not discuss or share LLM-related things outside of those channels.
## Creating and sharing a new project using strudel
Strudel is free/open source software, and we are also happy to see people making use of it within the following terms.
Please don't use 'strudel' in the name of your project, so people don't assume it's official strudel project. (If you'd like it to be an official strudel project, please check in with the community, e.g. on [the discord](https://discord.com/invite/HGEdXmRkzT).)
Please respect our AGPL license, which e.g. requires you to share/link to the source code of strudel, any modifications you've made to it, and the source code for the rest of your project if it integrates with strudel. You are also required to maintain Strudel's copyright notices in the source code, and include Strudel's copyright notice in your user interface. This is an ad-hoc summary - please [refer to the license](https://codeberg.org/uzu/strudel/src/branch/main/LICENSE) for full details.
You are also encouraged to connect with the community and understand our aims and values.
There are #llm-chat and #llm-share channels on our discord. Please do not discuss or share LLM-related things outside of those channels.
## Report a Bug
+21
View File
@@ -1457,6 +1457,27 @@ export function stack(...pats) {
return result;
}
/**
* The given items are played at the same time at the same length.
*
* @tags temporal
* @return {Pattern}
* @synonyms polyrhythm, pr
* @example
* mute_stack("g3", "b3", ["e4", "d4"]).note()
* // "g3,b3,[e4 d4]".note()
*
* @example
* // As a chained function:
* s("hh*4").mute_stack(
* note("c4(5,8)")
* )
*/
export function mute_stack(...pats) {
return silence;
}
function _stackWith(func, pats) {
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
if (pats.length === 0) {
+55 -13
View File
@@ -5,6 +5,12 @@ This program is free software: you can redistribute it and/or modify it under th
*/
// evolved from https://garten.salat.dev/lisp/parser.html
let recurse = 0
function lrec(...args) {
recurse += 1
console.info(recurse, ...args)
}
export class MondoParser {
// these are the tokens we expect
token_types = {
@@ -24,10 +30,17 @@ export class MondoParser {
op: /^[*/:!@%?+\-&]|^\.{2}/, // * / : ! @ % ? ..
// dollar: /^\$/,
pipe: /^#/,
stack: /^[,$]/,
// Matches _$ or _$BASS
mute_stack: /^_\$([a-zA-Z0-9_]+)?/,
// Matches S$ or S$VOCALS
solo_stack: /^S\$([a-zA-Z0-9_]+)?/,
// stack: /^[,$]/,
stack: /^,|^\$([a-zA-Z0-9_]+)?/,
or: /^[|]/,
plain: /^[a-zA-Z0-9-~_^#]+/,
};
solo_enabled = false;
op_precedence = [['*', '/', ':', '!', '@', '%', '?', '+', '-', '..'], ['&']];
// matches next token
next_token(code, offset = 0) {
@@ -62,6 +75,7 @@ export class MondoParser {
offset += token.value.length;
tokens.push(token);
}
lrec("TOKENS", tokens[0], tokens[1])
return tokens;
}
// take code, return abstract syntax tree
@@ -73,19 +87,22 @@ export class MondoParser {
while (this.tokens.length) {
expressions.push(this.parse_expr());
}
let parsed = expressions[0]
if (expressions.length === 0) {
// empty case
return { type: 'list', children: [] };
}
parsed = { type: 'list', children: [] };
} else if (expressions.length > 1 || expressions[0].type !== 'list')
// do we have multiple top level expressions or a single non list?
if (expressions.length > 1 || expressions[0].type !== 'list') {
return {
{
parsed = {
type: 'list',
children: this.desugar(expressions),
};
}
lrec("PARSED", parsed)
// we have a single list
return expressions[0];
return parsed;
}
// parses any valid expression
parse_expr() {
@@ -120,8 +137,11 @@ export class MondoParser {
children = children.slice(splitIndex + 1);
}
chunks.push(children);
lrec("chunks", chunks)
return chunks;
}
desugar_split(children, split_type, next) {
const chunks = this.split_children(children, split_type);
if (chunks.length === 1) {
@@ -253,12 +273,15 @@ export class MondoParser {
}
return node;
}
desugar(children, type) {
// if type is given, the first element is expected to contain it as plain value
// e.g. with (square a b, c), we want to split (a b, c) and ignore "square"
children = type ? children.slice(1) : children;
children = this.desugar_split(children, 'stack', (children) =>
this.desugar_split(children, 'or', (children) => {
const desugar_split_children = (children) => {
return this.desugar_split(children, 'or', (children) => {
console.info("TYPE", type)
// chunks of multiple args
if (type) {
// the type we've removed before splitting needs to be added back
@@ -269,14 +292,34 @@ export class MondoParser {
children = this.desugar_ops(children, ops);
});
children = this.desugar_pipes(children);
lrec("STACK CHILDREN", children)
return children;
}),
);
})
}
console.info("PRE_CHILDREN", children)
// children = this.desugar_split(children, 'mute_stack', (children) => {
// const x = desugar_split_children(children)
// // lrec({x})
// return x
// })
lrec({ children })
children = this.desugar_split(children, 'stack', (children) => {
return desugar_split_children(children)
})
lrec("CHILDREN", children)
return children;
}
parse_list() {
let node = this.parse_pair('open_list', 'close_list');
node.children = this.desugar(node.children);
lrec("node", node)
return node;
}
parse_angle() {
@@ -325,9 +368,8 @@ export function printAst(ast, compact = false, lvl = 0) {
const br = compact ? '' : '\n';
const spaces = compact ? '' : Array(lvl).fill(' ').join('');
if (ast.type === 'list') {
return `${lvl ? br : ''}${spaces}(${ast.children.map((child) => printAst(child, compact, lvl + 1)).join(' ')}${
ast.children.find((child) => child.type === 'list') ? `${br}${spaces})` : ')'
}`;
return `${lvl ? br : ''}${spaces}(${ast.children.map((child) => printAst(child, compact, lvl + 1)).join(' ')}${ast.children.find((child) => child.type === 'list') ? `${br}${spaces})` : ')'
}`;
}
return `${ast.value}`;
}
+11 -4
View File
@@ -49,10 +49,11 @@ lib['or'] = (...children) => chooseIn(...children); // always has structure but
//lib['or'] = (...children) => chooseOut(...children); // "s oh*8.dec[.04 | .5]" is better but "dec[.04 | .5].s oh*8" has no struct
function evaluator(node, scope) {
const { type } = node;
// node is list
if (type === 'list') {
const { children } = node;
const { type,children } = node;
// node is list]
if (type === 'list' && children.length) {
// const { children } = node;
const [name, ...args] = children;
// some functions wont be reified to make sure they work (e.g. see extend below)
if (typeof name === 'function') {
@@ -65,6 +66,7 @@ function evaluator(node, scope) {
const first = name.firstCycle(true)[0];
const type = typeof first?.value;
if (type !== 'function') {
console.error("first", first)
throw new Error(`[mondough] expected function, got "${first?.value}"`);
}
return name
@@ -76,12 +78,16 @@ function evaluator(node, scope) {
})
.innerJoin();
}
console.info("NODE", node)
// node is leaf
let { value } = node;
if (type === 'plain' && scope[value]) {
return reify(scope[value]); // -> local scope has no location
}
const variable = lib[value] ?? strudelScope[value];
console.info("VARIABLE", variable)
// problem: collisions when we want a string that happens to also be a variable name
// example: "s sine" -> sine is also a variable
let pat;
@@ -107,6 +113,7 @@ export function mondo(code, offset = 0) {
code = code.join('');
}
const pat = runner.run(code, undefined, offset);
console.info("MONDO_PAT", pat)
return pat.markcss('color: var(--caret,--foreground);text-decoration:underline');
}
-9
View File
@@ -52,15 +52,6 @@ There are multiple ways to load your sample collection. Some methods are good fo
- Serve a folder of samples locally using the [strudel 'sampler' commandline tool](https://strudel.cc/learn/samples/#from-disk-via-strudelsampler). This can be most reliable method, but requires [nodejs](https://nodejs.org) to be installed.
- Host your sound library online on the web and [load them from an URL](/learn/samples/#loading-custom-samples)
## Can I create a new project based on Strudel?
Strudel is free/open source software, and we are always happy to see people making use of it within the following terms:
- Please don't use 'strudel' in the name of your project (e.g. strudel2000, foo-strudel), so people don't assume it's official strudel project. (If you'd like it to be an official strudel project, please check in with the community, e.g. on the [discord chat](https://discord.com/invite/HGEdXmRkzT).)
- Please respect our AGPL license, which e.g. requires you to share/link to the source code of strudel, any modifications you've made to it, and the source code for the rest of your project if it integrates with strudel. You are also required to maintain Strudel's copyright notices in the source code, and include Strudel's copyright notice in your user interface. This is an ad-hoc summary - please [refer to the license](https://codeberg.org/uzu/strudel/src/branch/main/LICENSE) for full details.
You are also encouraged to connect with [the community](https://discord.com/invite/HGEdXmRkzT) to understand our aims and values.
## Can I use Strudel with AI/LLM tools?
You are free to do what you like with Strudel, within the terms of the free/open source AGPLv3 license.
@@ -36,9 +36,8 @@ export function WelcomeTab({ context }) {
<a href="https://tidalcycles.org/" target="_blank">
tidalcycles
</a>
, which is a popular live coding language for music, written in Haskell. Strudel is free/open source software,
with copyright owned by its [contributors](https://codeberg.org/uzu/strudel/activity/contributors). You can
redistribute and/or modify it under the terms of the{' '}
, which is a popular live coding language for music, written in Haskell. Strudel is free/open source software:
you can redistribute and/or modify it under the terms of the{' '}
<a href="https://codeberg.org/uzu/strudel/src/branch/main/LICENSE" target="_blank">
GNU Affero General Public License
</a>