mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-02 13:46:06 -04:00
Compare commits
2 Commits
name
..
mondo_solo
| Author | SHA1 | Date | |
|---|---|---|---|
| 8722c94bbe | |||
| b606bf4692 |
+1
-11
@@ -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.
|
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.
|
There are #llm-chat and #llm-share channels on our discord. 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.
|
|
||||||
|
|
||||||
## Report a Bug
|
## Report a Bug
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
- 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)
|
- 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?
|
## 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.
|
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">
|
<a href="https://tidalcycles.org/" target="_blank">
|
||||||
tidalcycles
|
tidalcycles
|
||||||
</a>
|
</a>
|
||||||
, which is a popular live coding language for music, written in Haskell. Strudel is free/open source software,
|
, 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
|
you can redistribute and/or modify it under the terms of the{' '}
|
||||||
redistribute and/or modify it under the terms of the{' '}
|
|
||||||
<a href="https://codeberg.org/uzu/strudel/src/branch/main/LICENSE" target="_blank">
|
<a href="https://codeberg.org/uzu/strudel/src/branch/main/LICENSE" target="_blank">
|
||||||
GNU Affero General Public License
|
GNU Affero General Public License
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user