mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-02 05:41:50 -04:00
Compare commits
5 Commits
mondo_solo
..
name
| Author | SHA1 | Date | |
|---|---|---|---|
| a866cb2189 | |||
| 890d69b23e | |||
| 8e187abf24 | |||
| 2d014b47c8 | |||
| daafee7527 |
+11
-1
@@ -52,7 +52,17 @@ 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. Please do not discuss or share LLM-related things outside of those channels.
|
||||
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.
|
||||
|
||||
## Report a Bug
|
||||
|
||||
|
||||
@@ -1457,27 +1457,6 @@ 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) {
|
||||
|
||||
+13
-55
@@ -5,12 +5,6 @@ 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 = {
|
||||
@@ -30,17 +24,10 @@ export class MondoParser {
|
||||
op: /^[*/:!@%?+\-&]|^\.{2}/, // * / : ! @ % ? ..
|
||||
// dollar: /^\$/,
|
||||
pipe: /^#/,
|
||||
// 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_]+)?/,
|
||||
stack: /^[,$]/,
|
||||
or: /^[|]/,
|
||||
plain: /^[a-zA-Z0-9-~_^#]+/,
|
||||
|
||||
};
|
||||
solo_enabled = false;
|
||||
op_precedence = [['*', '/', ':', '!', '@', '%', '?', '+', '-', '..'], ['&']];
|
||||
// matches next token
|
||||
next_token(code, offset = 0) {
|
||||
@@ -75,7 +62,6 @@ export class MondoParser {
|
||||
offset += token.value.length;
|
||||
tokens.push(token);
|
||||
}
|
||||
lrec("TOKENS", tokens[0], tokens[1])
|
||||
return tokens;
|
||||
}
|
||||
// take code, return abstract syntax tree
|
||||
@@ -87,22 +73,19 @@ export class MondoParser {
|
||||
while (this.tokens.length) {
|
||||
expressions.push(this.parse_expr());
|
||||
}
|
||||
let parsed = expressions[0]
|
||||
if (expressions.length === 0) {
|
||||
// empty case
|
||||
parsed = { type: 'list', children: [] };
|
||||
} else if (expressions.length > 1 || expressions[0].type !== 'list')
|
||||
return { type: 'list', children: [] };
|
||||
}
|
||||
// do we have multiple top level expressions or a single non list?
|
||||
{
|
||||
parsed = {
|
||||
if (expressions.length > 1 || expressions[0].type !== 'list') {
|
||||
return {
|
||||
type: 'list',
|
||||
children: this.desugar(expressions),
|
||||
};
|
||||
}
|
||||
|
||||
lrec("PARSED", parsed)
|
||||
// we have a single list
|
||||
return parsed;
|
||||
return expressions[0];
|
||||
}
|
||||
// parses any valid expression
|
||||
parse_expr() {
|
||||
@@ -137,11 +120,8 @@ 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) {
|
||||
@@ -273,15 +253,12 @@ 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;
|
||||
|
||||
const desugar_split_children = (children) => {
|
||||
return this.desugar_split(children, 'or', (children) => {
|
||||
console.info("TYPE", type)
|
||||
children = this.desugar_split(children, 'stack', (children) =>
|
||||
this.desugar_split(children, 'or', (children) => {
|
||||
// chunks of multiple args
|
||||
if (type) {
|
||||
// the type we've removed before splitting needs to be added back
|
||||
@@ -292,34 +269,14 @@ 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() {
|
||||
@@ -368,8 +325,9 @@ 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}`;
|
||||
}
|
||||
|
||||
@@ -49,11 +49,10 @@ 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,children } = node;
|
||||
// node is list]
|
||||
if (type === 'list' && children.length) {
|
||||
// const { children } = node;
|
||||
|
||||
const { type } = node;
|
||||
// node is list
|
||||
if (type === 'list') {
|
||||
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') {
|
||||
@@ -66,7 +65,6 @@ 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
|
||||
@@ -78,16 +76,12 @@ 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;
|
||||
@@ -113,7 +107,6 @@ 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');
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,15 @@ 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,8 +36,9 @@ 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:
|
||||
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,
|
||||
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{' '}
|
||||
<a href="https://codeberg.org/uzu/strudel/src/branch/main/LICENSE" target="_blank">
|
||||
GNU Affero General Public License
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user