Compare commits

..

4 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
yaxu c150752372 revert 85e6d436ef
revert motors workshop page
(committed to main by mistake!)
2026-06-21 20:47:04 +02:00
Alex McLean e248bf85f3 Merge pull request 'Creates a pattern of numbers in base x from a number or pattern of numbers @stretchyboy' (#2075) from stretchyboy-base-function into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/2075
2026-06-21 20:45:28 +02:00
4 changed files with 87 additions and 18 deletions
+21
View File
@@ -1457,6 +1457,27 @@ export function stack(...pats) {
return result; 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) { function _stackWith(func, pats) {
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat))); pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
if (pats.length === 0) { 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 // evolved from https://garten.salat.dev/lisp/parser.html
let recurse = 0
function lrec(...args) {
recurse += 1
console.info(recurse, ...args)
}
export class MondoParser { export class MondoParser {
// these are the tokens we expect // these are the tokens we expect
token_types = { token_types = {
@@ -24,10 +30,17 @@ export class MondoParser {
op: /^[*/:!@%?+\-&]|^\.{2}/, // * / : ! @ % ? .. op: /^[*/:!@%?+\-&]|^\.{2}/, // * / : ! @ % ? ..
// dollar: /^\$/, // dollar: /^\$/,
pipe: /^#/, 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: /^[|]/, or: /^[|]/,
plain: /^[a-zA-Z0-9-~_^#]+/, plain: /^[a-zA-Z0-9-~_^#]+/,
}; };
solo_enabled = false;
op_precedence = [['*', '/', ':', '!', '@', '%', '?', '+', '-', '..'], ['&']]; op_precedence = [['*', '/', ':', '!', '@', '%', '?', '+', '-', '..'], ['&']];
// matches next token // matches next token
next_token(code, offset = 0) { next_token(code, offset = 0) {
@@ -62,6 +75,7 @@ export class MondoParser {
offset += token.value.length; offset += token.value.length;
tokens.push(token); tokens.push(token);
} }
lrec("TOKENS", tokens[0], tokens[1])
return tokens; return tokens;
} }
// take code, return abstract syntax tree // take code, return abstract syntax tree
@@ -73,19 +87,22 @@ export class MondoParser {
while (this.tokens.length) { while (this.tokens.length) {
expressions.push(this.parse_expr()); expressions.push(this.parse_expr());
} }
let parsed = expressions[0]
if (expressions.length === 0) { if (expressions.length === 0) {
// empty case // 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? // do we have multiple top level expressions or a single non list?
if (expressions.length > 1 || expressions[0].type !== 'list') { {
return { parsed = {
type: 'list', type: 'list',
children: this.desugar(expressions), children: this.desugar(expressions),
}; };
} }
lrec("PARSED", parsed)
// we have a single list // we have a single list
return expressions[0]; return parsed;
} }
// parses any valid expression // parses any valid expression
parse_expr() { parse_expr() {
@@ -120,8 +137,11 @@ export class MondoParser {
children = children.slice(splitIndex + 1); children = children.slice(splitIndex + 1);
} }
chunks.push(children); chunks.push(children);
lrec("chunks", chunks)
return chunks; return chunks;
} }
desugar_split(children, split_type, next) { desugar_split(children, split_type, next) {
const chunks = this.split_children(children, split_type); const chunks = this.split_children(children, split_type);
if (chunks.length === 1) { if (chunks.length === 1) {
@@ -253,12 +273,15 @@ export class MondoParser {
} }
return node; return node;
} }
desugar(children, type) { desugar(children, type) {
// if type is given, the first element is expected to contain it as plain value // 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" // e.g. with (square a b, c), we want to split (a b, c) and ignore "square"
children = type ? children.slice(1) : children; 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 // chunks of multiple args
if (type) { if (type) {
// the type we've removed before splitting needs to be added back // 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_ops(children, ops);
}); });
children = this.desugar_pipes(children); children = this.desugar_pipes(children);
lrec("STACK CHILDREN", children)
return 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; return children;
} }
parse_list() { parse_list() {
let node = this.parse_pair('open_list', 'close_list'); let node = this.parse_pair('open_list', 'close_list');
node.children = this.desugar(node.children); node.children = this.desugar(node.children);
lrec("node", node)
return node; return node;
} }
parse_angle() { parse_angle() {
@@ -325,9 +368,8 @@ export function printAst(ast, compact = false, lvl = 0) {
const br = compact ? '' : '\n'; const br = compact ? '' : '\n';
const spaces = compact ? '' : Array(lvl).fill(' ').join(''); const spaces = compact ? '' : Array(lvl).fill(' ').join('');
if (ast.type === 'list') { if (ast.type === 'list') {
return `${lvl ? br : ''}${spaces}(${ast.children.map((child) => printAst(child, compact, lvl + 1)).join(' ')}${ return `${lvl ? br : ''}${spaces}(${ast.children.map((child) => printAst(child, compact, lvl + 1)).join(' ')}${ast.children.find((child) => child.type === 'list') ? `${br}${spaces})` : ')'
ast.children.find((child) => child.type === 'list') ? `${br}${spaces})` : ')' }`;
}`;
} }
return `${ast.value}`; 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 //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) { function evaluator(node, scope) {
const { type } = node; const { type,children } = node;
// node is list // node is list]
if (type === 'list') { if (type === 'list' && children.length) {
const { children } = node; // const { children } = node;
const [name, ...args] = children; const [name, ...args] = children;
// some functions wont be reified to make sure they work (e.g. see extend below) // some functions wont be reified to make sure they work (e.g. see extend below)
if (typeof name === 'function') { if (typeof name === 'function') {
@@ -65,6 +66,7 @@ function evaluator(node, scope) {
const first = name.firstCycle(true)[0]; const first = name.firstCycle(true)[0];
const type = typeof first?.value; const type = typeof first?.value;
if (type !== 'function') { if (type !== 'function') {
console.error("first", first)
throw new Error(`[mondough] expected function, got "${first?.value}"`); throw new Error(`[mondough] expected function, got "${first?.value}"`);
} }
return name return name
@@ -76,12 +78,16 @@ function evaluator(node, scope) {
}) })
.innerJoin(); .innerJoin();
} }
console.info("NODE", node)
// node is leaf // node is leaf
let { value } = node; let { value } = node;
if (type === 'plain' && scope[value]) { if (type === 'plain' && scope[value]) {
return reify(scope[value]); // -> local scope has no location return reify(scope[value]); // -> local scope has no location
} }
const variable = lib[value] ?? strudelScope[value]; 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 // problem: collisions when we want a string that happens to also be a variable name
// example: "s sine" -> sine is also a variable // example: "s sine" -> sine is also a variable
let pat; let pat;
@@ -107,6 +113,7 @@ export function mondo(code, offset = 0) {
code = code.join(''); code = code.join('');
} }
const pat = runner.run(code, undefined, offset); const pat = runner.run(code, undefined, offset);
console.info("MONDO_PAT", pat)
return pat.markcss('color: var(--caret,--foreground);text-decoration:underline'); return pat.markcss('color: var(--caret,--foreground);text-decoration:underline');
} }
-1
View File
@@ -68,7 +68,6 @@ export const SIDEBAR: Sidebar = {
{ text: 'Pattern Effects', link: 'workshop/pattern-effects' }, { text: 'Pattern Effects', link: 'workshop/pattern-effects' },
{ text: 'Recap', link: 'workshop/recap' }, { text: 'Recap', link: 'workshop/recap' },
{ text: 'Workshop in German', link: 'de/workshop/getting-started' }, { text: 'Workshop in German', link: 'de/workshop/getting-started' },
{ text: 'Tanglebot workshop', link: 'workshop/motors' },
], ],
'Making Sound': [ 'Making Sound': [
{ text: 'Samples', link: 'learn/samples' }, { text: 'Samples', link: 'learn/samples' },