From 5733e0090848ee8f7df5e652b4f2c90304788d85 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 7 Jan 2023 22:11:47 +0100 Subject: [PATCH 01/11] add hideDescription flag to JsDoc --- website/src/docs/JsDoc.jsx | 4 ++-- website/src/pages/technical-manual/docs.mdx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/website/src/docs/JsDoc.jsx b/website/src/docs/JsDoc.jsx index d75409c7..3c8dd1eb 100644 --- a/website/src/docs/JsDoc.jsx +++ b/website/src/docs/JsDoc.jsx @@ -2,7 +2,7 @@ import jsdoc from '../../../doc.json'; // doc.json is built with `npm run jsdoc- const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {}); import { MiniRepl } from './MiniRepl'; -export function JsDoc({ name, h = 3 }) { +export function JsDoc({ name, h = 3, hideDescription }) { const item = docs[name]; if (!item) { console.warn('Not found: ' + name); @@ -16,7 +16,7 @@ export function JsDoc({ name, h = 3 }) { return ( <> {!!h && {item.longname}} -
+ {!hideDescription &&
}
    {item.params?.map((param, i) => (
  • diff --git a/website/src/pages/technical-manual/docs.mdx b/website/src/pages/technical-manual/docs.mdx index 229b9a63..b0417984 100644 --- a/website/src/pages/technical-manual/docs.mdx +++ b/website/src/pages/technical-manual/docs.mdx @@ -47,6 +47,7 @@ Usage: - `name`: function name, as named with `@name` in jsdoc - `h`: level of heading. `0` will hide the heading. Hiding it allows using a manual heading which results in a nav link being generated in the right sidebar. +- `hideDescription`: if set, the description will be hidden ### Writing jsdoc From 62fdba0600246aac379b5c22591183e75aa18be8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 7 Jan 2023 22:12:35 +0100 Subject: [PATCH 02/11] pull apart functional docs --- packages/core/pattern.mjs | 6 +- website/src/config.ts | 14 +- website/src/pages/functions/intro.mdx | 40 +++ .../src/pages/functions/value-modifiers.mdx | 61 +++++ .../src/pages/learn/conditional-modifiers.mdx | 43 +++ website/src/pages/learn/factories.mdx | 91 +++++++ website/src/pages/learn/functions.mdx | 250 ------------------ website/src/pages/learn/time-modifiers.mdx | 64 +++++ 8 files changed, 314 insertions(+), 255 deletions(-) create mode 100644 website/src/pages/functions/intro.mdx create mode 100644 website/src/pages/functions/value-modifiers.mdx create mode 100644 website/src/pages/learn/conditional-modifiers.mdx create mode 100644 website/src/pages/learn/factories.mdx delete mode 100644 website/src/pages/learn/functions.mdx create mode 100644 website/src/pages/learn/time-modifiers.mdx diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 2af83df9..6ced2bae 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -712,7 +712,7 @@ export class Pattern { * @memberof Pattern * @example * s("hh*2").stack( - * n("c2(3,8)") + * note("c2(3,8)") * ) */ stack(...pats) { @@ -729,7 +729,7 @@ export class Pattern { * @memberof Pattern * @example * s("hh*2").seq( - * n("c2(3,8)") + * note("c2(3,8)") * ) */ seq(...pats) { @@ -742,7 +742,7 @@ export class Pattern { * @memberof Pattern * @example * s("hh*2").cat( - * n("c2(3,8)") + * note("c2(3,8)") * ) */ cat(...pats) { diff --git a/website/src/config.ts b/website/src/config.ts index 088782f1..31ded0f1 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -49,12 +49,22 @@ export const SIDEBAR: Sidebar = { { text: 'Sounds', link: 'learn/sounds' }, { text: 'Coding syntax', link: 'learn/code' }, { text: 'Mini-Notation', link: 'learn/mini-notation' }, + { text: 'Signals', link: 'learn/signals' }, + ], + 'Making Sound': [ { text: 'Samples', link: 'learn/samples' }, { text: 'Synths', link: 'learn/synths' }, { text: 'Audio Effects', link: 'learn/effects' }, - { text: 'Functions', link: 'learn/functions' }, - { text: 'Signals', link: 'learn/signals' }, + ], + Functions: [ + { text: 'Introduction', link: 'functions/intro' }, + { text: 'Value Modifiers', link: 'functions/value-modifiers' }, + { text: 'Factories', link: 'learn/factories' }, + { text: 'Time Modifiers', link: 'learn/time-modifiers' }, + { text: 'Conditional Modifiers', link: 'learn/conditional-modifiers' }, { text: 'Tonal', link: 'learn/tonal' }, + ], + More: [ { text: 'MIDI & OSC', link: 'learn/input-output' }, { text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' }, ], diff --git a/website/src/pages/functions/intro.mdx b/website/src/pages/functions/intro.mdx new file mode 100644 index 00000000..2e507e40 --- /dev/null +++ b/website/src/pages/functions/intro.mdx @@ -0,0 +1,40 @@ +--- +title: Introduction +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { JsDoc } from '../../docs/JsDoc'; + +# Functional JavaScript API + +While the mini notation is powerful on its own, there is much more to discover. +Internally, the mini notation will expand to use the actual functional JavaScript API. + +For example, this Pattern in Mini Notation: + + + +is equivalent to this Pattern without Mini Notation: + + + +Similarly, there is an equivalent function for every aspect of the mini notation. + +Which representation to use is a matter of context. As a rule of thumb, you can think of the JavaScript API +to fit better for the larger context, while mini notation is more practical for individiual rhythms. + +## Limits of Mini Notation + +While the Mini Notation is a powerful way to write rhythms shortly, it also has its limits. Take this example: + + + +Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them. +While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds. diff --git a/website/src/pages/functions/value-modifiers.mdx b/website/src/pages/functions/value-modifiers.mdx new file mode 100644 index 00000000..1f949b01 --- /dev/null +++ b/website/src/pages/functions/value-modifiers.mdx @@ -0,0 +1,61 @@ +--- +title: Value Modifiers +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { JsDoc } from '../../docs/JsDoc'; + +# Value Modifiers + +## Notes + +Notes are automatically available as variables: + + + +An important difference to the mini notation: +For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name. + +The above is the same as: + + + +Using strings, you can also use "#". + +## Alternative Syntax + +In the above example, we are nesting a function inside a function, which makes reading the parens a little more difficult. +To avoid getting to many nested parens, there is an alternative syntax to add a type to a pattern: + + + +You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty! + +## Pattern.add + + + +## Pattern.sub + + + +## Pattern.mul + + + +## Pattern.div + + + +## Pattern.round + + + +## Pattern.apply + + + +## Pattern.range + + diff --git a/website/src/pages/learn/conditional-modifiers.mdx b/website/src/pages/learn/conditional-modifiers.mdx new file mode 100644 index 00000000..6e4966df --- /dev/null +++ b/website/src/pages/learn/conditional-modifiers.mdx @@ -0,0 +1,43 @@ +--- +title: Conditional Modifiers +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { JsDoc } from '../../docs/JsDoc'; + +# Conditional Modifiers + +## every + + + +## when + + + +# Accumulation Modifiers + +## stack + + + +## superimpose + + + +## layer + + + +## off + + + +## echo + + + +## echoWith + + diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx new file mode 100644 index 00000000..957ecf78 --- /dev/null +++ b/website/src/pages/learn/factories.mdx @@ -0,0 +1,91 @@ +--- +title: Pattern Factories +description: Strudel Tutorial +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { JsDoc } from '../../docs/JsDoc'; + +# Pattern Factories + +The following functions will return a pattern. +Most of these are also used by the Mini Notation: + +| function | mini | +| ------------------------------ | --------------- | +| `cat(x, y)` | `""` | +| `seq(x, y)` | `"x y"` | +| `stack(x, y)` | `"x,y"` | +| `timeCat([3,x],[2,y])` | `"x@2 y@2"` | +| `polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` | +| `polymeterSteps(2, x, y, z)` | `"{x y z}%2"` | + +## cat + + + +You can also use cat as a chained function like this: + + + +## seq + + + +Or as a chained function: + + + +## stack + + + +As a chained function: + + + +## timeCat + + + +## polymeter + + + +## polymeterSteps + + + +# Combining Patterns + +You can freely mix JS patterns, mini patterns and values! For example, this pattern: + + + +...is equivalent to: + + + +... as well as: + +")`} /> + +While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @. +When using JS patterns, there is a lot more you can do. diff --git a/website/src/pages/learn/functions.mdx b/website/src/pages/learn/functions.mdx deleted file mode 100644 index 0cc83dc2..00000000 --- a/website/src/pages/learn/functions.mdx +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: What is Strudel? -description: Strudel Tutorial -layout: ../../layouts/MainLayout.astro ---- - -import { MiniRepl } from '../../docs/MiniRepl'; -import { JsDoc } from '../../docs/JsDoc'; - -# Functional JavaScript API - -While the mini notation is powerful on its own, there is much more to discover. -Internally, the mini notation will expand to use the actual functional JavaScript API. - -For example, this Pattern in Mini Notation: - - - -is equivalent to this Pattern without Mini Notation: - - - -Similarly, there is an equivalent function for every aspect of the mini notation. - -Which representation to use is a matter of context. As a rule of thumb, you can think of the JavaScript API -to fit better for the larger context, while mini notation is more practical for individiual rhythms. - -## Limits of Mini Notation - -While the Mini Notation is a powerful way to write rhythms shortly, it also has its limits. Take this example: - - - -Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them. -While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds. - -## Notes - -Notes are automatically available as variables: - - - -An important difference to the mini notation: -For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name. - -The above is the same as: - - - -Using strings, you can also use "#". - -## Alternative Syntax - -In the above example, we are nesting a function inside a function, which makes reading the parens a little more difficult. -To avoid getting to many nested parens, there is an alternative syntax to add a type to a pattern: - - - -You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty! - -## Pattern Factories - -The following functions will return a pattern. - -### cat - - - -### seq - - - -### stack - - - -### timeCat - - - -## Combining Patterns - -You can freely mix JS patterns, mini patterns and values! For example, this pattern: - - - -...is equivalent to: - - - -... as well as: - -")`} /> - -While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @. -When using JS patterns, there is a lot more you can do. - -## Time Modifiers - -The following functions modify a pattern temporal structure in some way. - -### Pattern.slow - - - -### Pattern.fast - - - -### Pattern.early - - - -### Pattern.late - - - -### Pattern.legato - - - -### Pattern.struct - - - -### Pattern.euclid - - - -### Pattern.euclidLegato - - - -### Pattern.rev - - - -### Pattern.iter - - - -### Pattern.iterBack - - - -## Conditional Modifiers - -### Pattern.every - - - -### Pattern.when - - - -## Accumulation Modifiers - -### Pattern.stack - - - -### Pattern.superimpose - - - -### Pattern.layer - - - -### Pattern.off - - - -### Pattern.echo - - - -### Pattern.echoWith - - - -## Concat Modifiers - -### Pattern.seq - - - -### Pattern.cat - - - -## Value Modifiers - -### Pattern.add - - - -### Pattern.sub - - - -### Pattern.mul - - - -### Pattern.div - - - -### Pattern.round - - - -### Pattern.apply - - - -### Pattern.range - - - -### Pattern.chunk - - - -### Pattern.chunkBack - - diff --git a/website/src/pages/learn/time-modifiers.mdx b/website/src/pages/learn/time-modifiers.mdx new file mode 100644 index 00000000..7e41ed34 --- /dev/null +++ b/website/src/pages/learn/time-modifiers.mdx @@ -0,0 +1,64 @@ +--- +title: Pattern Factories +description: Strudel Tutorial +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { JsDoc } from '../../docs/JsDoc'; + +# Time Modifiers + +The following functions modify a pattern temporal structure in some way. + +## slow + + + +## fast + + + +## early + + + +## late + + + +## legato + + + +## struct + + + +## euclid + + + +## euclidLegato + + + +## rev + + + +## iter + + + +## iterBack + + + +### chunk + + + +### chunkBack + + From 70695daae89c4611b3ecb5f9923eb1e945f7f144 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 7 Jan 2023 22:12:53 +0100 Subject: [PATCH 03/11] remove quotes around inline code snippets --- website/tailwind.config.cjs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/website/tailwind.config.cjs b/website/tailwind.config.cjs index 52707838..7d263336 100644 --- a/website/tailwind.config.cjs +++ b/website/tailwind.config.cjs @@ -1,4 +1,7 @@ /** @type {import('tailwindcss').Config} */ + +const defaultTheme = require('tailwindcss/defaultTheme'); + module.exports = { content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], theme: { @@ -17,6 +20,20 @@ module.exports = { // header: 'transparent', footer: '#00000050', }, + typography(theme) { + return { + DEFAULT: { + css: { + 'code::before': { + content: 'none', // don’t wrap code in backticks + }, + 'code::after': { + content: 'none', + }, + }, + }, + }; + }, }, }, plugins: [require('@tailwindcss/typography')], From 22a64c49317a971853f809ed6ca2431f988a6547 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 8 Jan 2023 23:46:39 +0100 Subject: [PATCH 04/11] update snapshots --- test/__snapshots__/examples.test.mjs.snap | 60 +++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 49a1571d..8f9fe9f2 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -899,14 +899,14 @@ exports[`runs examples > example "cat" example index 0 1`] = ` [ "[ 0/1 → 1/2 | s:hh ]", "[ 1/2 → 1/1 | s:hh ]", - "[ 1/1 → 9/8 | n:c2 ]", - "[ 11/8 → 3/2 | n:c2 ]", - "[ 7/4 → 15/8 | n:c2 ]", + "[ 1/1 → 9/8 | note:c2 ]", + "[ 11/8 → 3/2 | note:c2 ]", + "[ 7/4 → 15/8 | note:c2 ]", "[ 2/1 → 5/2 | s:hh ]", "[ 5/2 → 3/1 | s:hh ]", - "[ 3/1 → 25/8 | n:c2 ]", - "[ 27/8 → 7/2 | n:c2 ]", - "[ 15/4 → 31/8 | n:c2 ]", + "[ 3/1 → 25/8 | note:c2 ]", + "[ 27/8 → 7/2 | note:c2 ]", + "[ 15/4 → 31/8 | note:c2 ]", ] `; @@ -2662,24 +2662,24 @@ exports[`runs examples > example "seq" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:hh ]", "[ 1/4 → 1/2 | s:hh ]", - "[ 1/2 → 9/16 | n:c2 ]", - "[ 11/16 → 3/4 | n:c2 ]", - "[ 7/8 → 15/16 | n:c2 ]", + "[ 1/2 → 9/16 | note:c2 ]", + "[ 11/16 → 3/4 | note:c2 ]", + "[ 7/8 → 15/16 | note:c2 ]", "[ 1/1 → 5/4 | s:hh ]", "[ 5/4 → 3/2 | s:hh ]", - "[ 3/2 → 25/16 | n:c2 ]", - "[ 27/16 → 7/4 | n:c2 ]", - "[ 15/8 → 31/16 | n:c2 ]", + "[ 3/2 → 25/16 | note:c2 ]", + "[ 27/16 → 7/4 | note:c2 ]", + "[ 15/8 → 31/16 | note:c2 ]", "[ 2/1 → 9/4 | s:hh ]", "[ 9/4 → 5/2 | s:hh ]", - "[ 5/2 → 41/16 | n:c2 ]", - "[ 43/16 → 11/4 | n:c2 ]", - "[ 23/8 → 47/16 | n:c2 ]", + "[ 5/2 → 41/16 | note:c2 ]", + "[ 43/16 → 11/4 | note:c2 ]", + "[ 23/8 → 47/16 | note:c2 ]", "[ 3/1 → 13/4 | s:hh ]", "[ 13/4 → 7/2 | s:hh ]", - "[ 7/2 → 57/16 | n:c2 ]", - "[ 59/16 → 15/4 | n:c2 ]", - "[ 31/8 → 63/16 | n:c2 ]", + "[ 7/2 → 57/16 | note:c2 ]", + "[ 59/16 → 15/4 | note:c2 ]", + "[ 31/8 → 63/16 | note:c2 ]", ] `; @@ -2956,18 +2956,18 @@ exports[`runs examples > example "stack" example index 0 1`] = ` "[ 5/2 → 3/1 | s:hh ]", "[ 3/1 → 7/2 | s:hh ]", "[ 7/2 → 4/1 | s:hh ]", - "[ 0/1 → 1/8 | n:c2 ]", - "[ 3/8 → 1/2 | n:c2 ]", - "[ 3/4 → 7/8 | n:c2 ]", - "[ 1/1 → 9/8 | n:c2 ]", - "[ 11/8 → 3/2 | n:c2 ]", - "[ 7/4 → 15/8 | n:c2 ]", - "[ 2/1 → 17/8 | n:c2 ]", - "[ 19/8 → 5/2 | n:c2 ]", - "[ 11/4 → 23/8 | n:c2 ]", - "[ 3/1 → 25/8 | n:c2 ]", - "[ 27/8 → 7/2 | n:c2 ]", - "[ 15/4 → 31/8 | n:c2 ]", + "[ 0/1 → 1/8 | note:c2 ]", + "[ 3/8 → 1/2 | note:c2 ]", + "[ 3/4 → 7/8 | note:c2 ]", + "[ 1/1 → 9/8 | note:c2 ]", + "[ 11/8 → 3/2 | note:c2 ]", + "[ 7/4 → 15/8 | note:c2 ]", + "[ 2/1 → 17/8 | note:c2 ]", + "[ 19/8 → 5/2 | note:c2 ]", + "[ 11/4 → 23/8 | note:c2 ]", + "[ 3/1 → 25/8 | note:c2 ]", + "[ 27/8 → 7/2 | note:c2 ]", + "[ 15/4 → 31/8 | note:c2 ]", ] `; From 09217216dc811938b492b62dc917c0c4d3ef0d59 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 8 Jan 2023 23:48:32 +0100 Subject: [PATCH 05/11] format --- website/src/pages/learn/factories.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx index 957ecf78..03da950a 100644 --- a/website/src/pages/learn/factories.mdx +++ b/website/src/pages/learn/factories.mdx @@ -12,11 +12,11 @@ import { JsDoc } from '../../docs/JsDoc'; The following functions will return a pattern. Most of these are also used by the Mini Notation: -| function | mini | -| ------------------------------ | --------------- | -| `cat(x, y)` | `""` | -| `seq(x, y)` | `"x y"` | -| `stack(x, y)` | `"x,y"` | +| function | mini | +| ------------------------------ | ---------------- | +| `cat(x, y)` | `""` | +| `seq(x, y)` | `"x y"` | +| `stack(x, y)` | `"x,y"` | | `timeCat([3,x],[2,y])` | `"x@2 y@2"` | | `polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` | | `polymeterSteps(2, x, y, z)` | `"{x y z}%2"` | From bb00036a30af8c05abf1bad71ad5c47d3eafb18f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Jan 2023 00:17:57 +0100 Subject: [PATCH 06/11] further sidebar reorganizing --- website/src/config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/src/config.ts b/website/src/config.ts index 31ded0f1..5290d65c 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -49,7 +49,6 @@ export const SIDEBAR: Sidebar = { { text: 'Sounds', link: 'learn/sounds' }, { text: 'Coding syntax', link: 'learn/code' }, { text: 'Mini-Notation', link: 'learn/mini-notation' }, - { text: 'Signals', link: 'learn/signals' }, ], 'Making Sound': [ { text: 'Samples', link: 'learn/samples' }, @@ -60,18 +59,19 @@ export const SIDEBAR: Sidebar = { { text: 'Introduction', link: 'functions/intro' }, { text: 'Value Modifiers', link: 'functions/value-modifiers' }, { text: 'Factories', link: 'learn/factories' }, + { text: 'Signals', link: 'learn/signals' }, { text: 'Time Modifiers', link: 'learn/time-modifiers' }, { text: 'Conditional Modifiers', link: 'learn/conditional-modifiers' }, { text: 'Tonal', link: 'learn/tonal' }, ], More: [ + { text: 'Patterns', link: 'technical-manual/patterns' }, + { text: 'Pattern Alignment', link: 'technical-manual/alignment' }, { text: 'MIDI & OSC', link: 'learn/input-output' }, { text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' }, ], - 'Technical Manual': [ - { text: 'Patterns', link: 'technical-manual/patterns' }, + Development: [ { text: 'REPL', link: 'technical-manual/repl' }, - { text: 'Pattern Alignment', link: 'technical-manual/alignment' }, { text: 'Docs', link: 'technical-manual/docs' }, { text: 'Testing', link: 'technical-manual/testing' }, ], From 9ca5f9ad48da27e7cb507c5b9c5fb19bd49f2691 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Jan 2023 00:19:29 +0100 Subject: [PATCH 07/11] some corrections + n should not be recommended for notes as it does not work for samples --- website/src/pages/learn/notes.mdx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/website/src/pages/learn/notes.mdx b/website/src/pages/learn/notes.mdx index 6bfd3b3d..9fe8472f 100644 --- a/website/src/pages/learn/notes.mdx +++ b/website/src/pages/learn/notes.mdx @@ -10,16 +10,16 @@ import { JsDoc } from '../../docs/JsDoc'; # Notes Pitches are an essential building block for music. -In Strudel, there are three different ways to express a pitch, `note`, `n` and `freq`. +In Strudel, pitches can be expressed as note names, note numbers or frequencies. Here's the same pattern written in three different ways: - `note`: letter notation, good for those who are familiar with western music theory: -- `n`: number notation, good for those who want to use recognisable pitches, but don't care about music theory: +- `note`: number notation, good for those who want to use recognisable pitches, but don't care about music theory: - + - `freq`: frequency notation, good for those who want to go beyond standardised tuning systems: @@ -27,28 +27,28 @@ Here's the same pattern written in three different ways: Let's look at `note`, `n` and `freq` in more detail... -# `note` +## `note` names -Notes are notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`. +Notes names can be notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`. By the way, you can edit the contents of the player, and press "update" to hear your change! You can also press "play" on the next player without needing to stop the last one. -# `n` +## `note` numbers -If you prefer, you can also use numbers with `n` instead: +If you prefer, you can also use numbers with `note` instead: - + These numbers are interpreted as so called [MIDI numbers](https://www.inspiredacoustics.com/en/MIDI_note_numbers_and_center_frequencies), where adjacent whole numbers are one 'semitone' apart. You could also write decimal numbers to get 'microtonal' pitches (in between the black and white piano notes): - + -# `freq` +## `freq` To get maximum freedom, you can also use `freq` to directly control the frequency: @@ -76,13 +76,13 @@ The less distance we can hear between the frequencies! Why is this? [Human hearing operates logarithmically](https://www.audiocheck.net/soundtests_nonlinear.php). -# From notes to sounds +## From notes to sounds In this page, when we played a pattern of notes like this: -We heard a simple synthesised sound, in fact we heard a [square wave oscillator](https://en.wikipedia.org/wiki/Square_wave). +We heard a simple synthesised sound, in fact we heard a [triangle wave oscillator](https://en.wikipedia.org/wiki/Triangle_wave). This is the default synthesiser used by Strudel, but how do we then make different sounds in Strudel? From 10ee11c8860fa297e089b4b3e5cb44a57820c09d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Jan 2023 20:39:00 +0100 Subject: [PATCH 08/11] docs: improve time modifiers page + polymeter + euclidRot + rename Factories to Pattern Constructors --- packages/core/euclid.mjs | 2 +- packages/core/pattern.mjs | 20 ++++++++++- website/src/config.ts | 10 +++--- website/src/pages/functions/intro.mdx | 33 ++++++++++++++++++ website/src/pages/learn/factories.mdx | 39 ++-------------------- website/src/pages/learn/time-modifiers.mdx | 21 +++++++++--- 6 files changed, 77 insertions(+), 48 deletions(-) diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index 6a5be89b..6aa2283d 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -73,7 +73,7 @@ const bjork = function (ons, steps) { */ /** - * Like `iter`, but has an additional parameter for 'rotating' the resulting sequence. + * Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence. * @memberof Pattern * @name euclidRot * @param {number} pulses the number of onsets / beats diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 6ced2bae..17187bd6 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1215,7 +1215,17 @@ function _sequenceCount(x) { } return [reify(x), 1]; } - +/** + * Aligns one or more given sequences to the given number of steps per cycle. + * + * @name polymeterSteps + * @param {number} steps how many items are placed in one cycle + * @param {any[]} sequences one or more arrays of Patterns / values + * @example + * polymeterSteps(2, ["c", "d", "e", "f", "g", "f", "e", "d"]) + * .note().stack(s("bd")) // 1 cycle = 1 bd = 2 notes + * // note("{c d e f g f e d}%2").stack(s("bd")) + */ export function polymeterSteps(steps, ...args) { const seqs = args.map((a) => _sequenceCount(a)); if (seqs.length == 0) { @@ -1238,6 +1248,14 @@ export function polymeterSteps(steps, ...args) { return stack(...pats); } +/** + * Combines the given lists of patterns with the same pulse. This will create so called polymeters when different sized sequences are used. + * @name polymeter + * @example + * polymeter(["c", "eb", "g"], ["c2", "g2"]).note() + * // "{c eb g, c2 g2}".note() + * + */ export function polymeter(...args) { return polymeterSteps(0, ...args); } diff --git a/website/src/config.ts b/website/src/config.ts index 5290d65c..3d5c6069 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -55,14 +55,14 @@ export const SIDEBAR: Sidebar = { { text: 'Synths', link: 'learn/synths' }, { text: 'Audio Effects', link: 'learn/effects' }, ], - Functions: [ + 'Pattern Functions': [ { text: 'Introduction', link: 'functions/intro' }, - { text: 'Value Modifiers', link: 'functions/value-modifiers' }, - { text: 'Factories', link: 'learn/factories' }, - { text: 'Signals', link: 'learn/signals' }, + { text: 'Pattern Constructors', link: 'learn/factories' }, { text: 'Time Modifiers', link: 'learn/time-modifiers' }, + { text: 'Value Modifiers', link: 'functions/value-modifiers' }, + { text: 'Signals', link: 'learn/signals' }, { text: 'Conditional Modifiers', link: 'learn/conditional-modifiers' }, - { text: 'Tonal', link: 'learn/tonal' }, + { text: 'Tonal Modifiers', link: 'learn/tonal' }, ], More: [ { text: 'Patterns', link: 'technical-manual/patterns' }, diff --git a/website/src/pages/functions/intro.mdx b/website/src/pages/functions/intro.mdx index 2e507e40..bed3cb37 100644 --- a/website/src/pages/functions/intro.mdx +++ b/website/src/pages/functions/intro.mdx @@ -38,3 +38,36 @@ While the Mini Notation is a powerful way to write rhythms shortly, it also has Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them. While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds. + +## Combining Patterns + +You can freely mix JS patterns, mini patterns and values! For example, this pattern: + + + +...is equivalent to: + + + +... as well as: + +")`} /> + +While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @. +When using JS patterns, there is a lot more you can do. diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx index 03da950a..7bce19df 100644 --- a/website/src/pages/learn/factories.mdx +++ b/website/src/pages/learn/factories.mdx @@ -1,5 +1,5 @@ --- -title: Pattern Factories +title: Pattern Constructors description: Strudel Tutorial layout: ../../layouts/MainLayout.astro --- @@ -7,10 +7,10 @@ layout: ../../layouts/MainLayout.astro import { MiniRepl } from '../../docs/MiniRepl'; import { JsDoc } from '../../docs/JsDoc'; -# Pattern Factories +# Pattern Constructors The following functions will return a pattern. -Most of these are also used by the Mini Notation: +These are the equivalents used by the Mini Notation: | function | mini | | ------------------------------ | ---------------- | @@ -56,36 +56,3 @@ As a chained function: ## polymeterSteps - -# Combining Patterns - -You can freely mix JS patterns, mini patterns and values! For example, this pattern: - - - -...is equivalent to: - - - -... as well as: - -")`} /> - -While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @. -When using JS patterns, there is a lot more you can do. diff --git a/website/src/pages/learn/time-modifiers.mdx b/website/src/pages/learn/time-modifiers.mdx index 7e41ed34..c24d5961 100644 --- a/website/src/pages/learn/time-modifiers.mdx +++ b/website/src/pages/learn/time-modifiers.mdx @@ -1,6 +1,5 @@ --- -title: Pattern Factories -description: Strudel Tutorial +title: Time Modifiers layout: ../../layouts/MainLayout.astro --- @@ -10,6 +9,14 @@ import { JsDoc } from '../../docs/JsDoc'; # Time Modifiers The following functions modify a pattern temporal structure in some way. +Some of these have equivalent operators in the Mini Notation: + +| function | mini | +| ---------------------- | ------------ | +| `"x".slow(2)` | `"x/2"` | +| `"x".fast(2)` | `"x*2"` | +| `"x".euclid(3,8)` | `"x(3,8)"` | +| `"x".euclidRot(3,8,1)` | `"x(3,8,1)"` | ## slow @@ -39,7 +46,11 @@ The following functions modify a pattern temporal structure in some way. -## euclidLegato +### euclidRot + + + +### euclidLegato @@ -51,11 +62,11 @@ The following functions modify a pattern temporal structure in some way. -## iterBack +### iterBack -### chunk +## chunk From 69ecb7b54fc9b967b725fc9951bacd42ea278085 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Jan 2023 23:26:20 +0100 Subject: [PATCH 09/11] support logs in mini repl + pass hap to logger + add editPattern hook to repl + useStrudel + do not throw when webaudio gets plain values --- packages/core/pattern.mjs | 21 +- packages/core/repl.mjs | 5 +- packages/react/dist/index.cjs.js | 2 +- packages/react/dist/index.es.js | 461 +++++++++++---------- packages/react/dist/style.css | 2 +- packages/react/src/components/MiniRepl.jsx | 43 +- packages/react/src/hooks/useStrudel.mjs | 2 + packages/webaudio/webaudio.mjs | 9 +- 8 files changed, 317 insertions(+), 228 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 17187bd6..d3fc7b4d 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -776,8 +776,10 @@ export class Pattern { ); } - log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`) { - return this.onTrigger((...args) => logger(func(...args)), false); + log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) { + return this.onTrigger((...args) => { + logger(func(...args), undefined, getData(...args)); + }, false); } logValues(func = id) { @@ -1045,7 +1047,12 @@ Pattern.prototype.factories = { // Elemental patterns -// Nothing +/** + * Does absolutely nothing.. + * @name silence + * @example + * silence // "~" + */ export const silence = new Pattern(() => []); /** A discrete value that repeats once per cycle. @@ -1372,7 +1379,11 @@ export const round = register('round', function (pat) { * Assumes a numerical pattern. Returns a new pattern with all values set to * their mathematical floor. E.g. `3.7` replaced with to `3`, and `-4.2` * replaced with `-5`. + * @name floor + * @memberof Pattern * @returns Pattern + * @example + * "42 42.1 42.5 43".floor().note() */ export const floor = register('floor', function (pat) { return pat.asNumber().fmap((v) => Math.floor(v)); @@ -1382,7 +1393,11 @@ export const floor = register('floor', function (pat) { * Assumes a numerical pattern. Returns a new pattern with all values set to * their mathematical ceiling. E.g. `3.2` replaced with `4`, and `-4.2` * replaced with `-4`. + * @name ceil + * @memberof Pattern * @returns Pattern + * @example + * "42 42.1 42.5 43".ceil().note() */ export const ceil = register('ceil', function (pat) { return pat.asNumber().fmap((v) => Math.ceil(v)); diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 6bef319b..c71019ac 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -12,6 +12,7 @@ export function repl({ afterEval, getTime, transpiler, + editPattern, onToggle, }) { const scheduler = new Cyclist({ @@ -41,8 +42,10 @@ export function repl({ } try { beforeEval?.({ code }); - const { pattern } = await _evaluate(code, transpiler); + let { pattern } = await _evaluate(code, transpiler); + logger(`[eval] code updated`); + pattern = editPattern?.(pattern) || pattern; scheduler.setPattern(pattern, autostart); afterEval?.({ code, pattern }); return pattern; diff --git a/packages/react/dist/index.cjs.js b/packages/react/dist/index.cjs.js index bdcd41d6..5e9d79dc 100644 --- a/packages/react/dist/index.cjs.js +++ b/packages/react/dist/index.cjs.js @@ -1 +1 @@ -"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),Z=require("@uiw/react-codemirror"),w=require("@codemirror/view"),x=require("@codemirror/state"),ee=require("@codemirror/lang-javascript"),c=require("@lezer/highlight"),te=require("@uiw/codemirror-themes"),B=require("@strudel.cycles/core"),K=require("@strudel.cycles/webaudio"),re=require("react-hook-inview"),ae=require("@strudel.cycles/transpiler"),I=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},l=I(t),oe=I(Z),ne=te.createTheme({theme:"dark",settings:{background:"#222",foreground:"#75baff",caret:"#ffcc00",selection:"rgba(128, 203, 196, 0.5)",selectionMatch:"#036dd626",lineHighlight:"#00000050",gutterBackground:"transparent",gutterForeground:"#8a919966"},styles:[{tag:c.tags.keyword,color:"#c792ea"},{tag:c.tags.operator,color:"#89ddff"},{tag:c.tags.special(c.tags.variableName),color:"#eeffff"},{tag:c.tags.typeName,color:"#c3e88d"},{tag:c.tags.atom,color:"#f78c6c"},{tag:c.tags.number,color:"#c3e88d"},{tag:c.tags.definition(c.tags.variableName),color:"#82aaff"},{tag:c.tags.string,color:"#c3e88d"},{tag:c.tags.special(c.tags.string),color:"#c3e88d"},{tag:c.tags.comment,color:"#7d8799"},{tag:c.tags.variableName,color:"#c792ea"},{tag:c.tags.tagName,color:"#c3e88d"},{tag:c.tags.bracket,color:"#525154"},{tag:c.tags.meta,color:"#ffcb6b"},{tag:c.tags.attributeName,color:"#c792ea"},{tag:c.tags.propertyName,color:"#c792ea"},{tag:c.tags.className,color:"#decb6b"},{tag:c.tags.invalid,color:"#ffffff"}]});const L=x.StateEffect.define(),se=x.StateField.define({create(){return w.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(L))if(r.value){const s=w.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=w.Decoration.set([s.range(0,a.newDoc.length)])}else e=w.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>w.EditorView.decorations.from(e)}),U=e=>{e.dispatch({effects:L.of(!0)}),setTimeout(()=>{e.dispatch({effects:L.of(!1)})},200)},A=x.StateEffect.define(),ce=x.StateField.define({create(){return w.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(A)){const s=r.value.map(n=>(n.context.locations||[]).map(({start:i,end:u})=>{const f=n.context.color||"#FFCA28";let o=a.newDoc.line(i.line).from+i.column,d=a.newDoc.line(u.line).from+u.column;const g=a.newDoc.length;return o>g||d>g?void 0:w.Decoration.mark({attributes:{style:`outline: 1.5px solid ${f};`}}).range(o,d)})).flat().filter(Boolean)||[];e=w.Decoration.set(s,!0)}return e}catch{return w.Decoration.set([])}},provide:e=>w.EditorView.decorations.from(e)}),ie=[ee.javascript(),ne,ce,se];function W({value:e,onChange:a,onViewChanged:r,onSelectionChange:s,options:n,editorDidMount:i}){const u=t.useCallback(d=>{a?.(d)},[a]),f=t.useCallback(d=>{r?.(d)},[r]),o=t.useCallback(d=>{d.selectionSet&&s&&s?.(d.state.selection)},[s]);return l.default.createElement(l.default.Fragment,null,l.default.createElement(oe.default,{value:e,onChange:u,onCreateEditor:f,onUpdate:o,extensions:ie}))}function z(...e){return e.filter(Boolean).join(" ")}function $({view:e,pattern:a,active:r,getTime:s}){const n=t.useRef([]),i=t.useRef();t.useEffect(()=>{if(e)if(a&&r){let u=requestAnimationFrame(function f(){try{const o=s(),g=[Math.max(i.current||o,o-1/10,0),o+1/60];i.current=g[1],n.current=n.current.filter(v=>v.whole.end>o);const m=a.queryArc(...g).filter(v=>v.hasOnset());n.current=n.current.concat(m),e.dispatch({effects:A.of(n.current)})}catch{e.dispatch({effects:A.of([])})}u=requestAnimationFrame(f)});return()=>{cancelAnimationFrame(u)}}else n.current=[],e.dispatch({effects:A.of([])})},[a,r,e])}function le(e,a=!1){const r=t.useRef(),s=t.useRef(),n=f=>{if(s.current!==void 0){const o=f-s.current;e(f,o)}s.current=f,r.current=requestAnimationFrame(n)},i=()=>{r.current=requestAnimationFrame(n)},u=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(u(),i())},[e]),t.useEffect(()=>(a&&i(),u),[]),{start:i,stop:u}}function ue({pattern:e,started:a,getTime:r,onDraw:s}){let n=t.useRef([]),i=t.useRef(null);const{start:u,stop:f}=le(t.useCallback(()=>{const o=r();if(i.current===null){i.current=o;return}const d=e.queryArc(Math.max(i.current,o-1/10),o),g=4;i.current=o,n.current=(n.current||[]).filter(m=>m.whole.end>o-g).concat(d.filter(m=>m.hasOnset())),s(o,n.current)},[e]));t.useEffect(()=>{a?u():(n.current=[],f())},[a])}function J(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(a=>window.postMessage(a,"*"),[])}function G({defaultOutput:e,interval:a,getTime:r,evalOnMount:s=!1,initialCode:n="",autolink:i=!1,beforeEval:u,afterEval:f,onEvalError:o,onToggle:d,canvasId:g}){const m=t.useMemo(()=>de(),[]);g=g||`canvas-${m}`;const[v,k]=t.useState(),[C,q]=t.useState(),[E,M]=t.useState(n),[_,T]=t.useState(),[P,D]=t.useState(),[F,H]=t.useState(!1),b=E!==_,{scheduler:h,evaluate:R,start:Q,stop:V,pause:X}=t.useMemo(()=>B.repl({interval:a,defaultOutput:e,onSchedulerError:k,onEvalError:p=>{q(p),o?.(p)},getTime:r,transpiler:ae.transpiler,beforeEval:({code:p})=>{M(p),u?.()},afterEval:({pattern:p,code:N})=>{T(N),D(p),q(),k(),i&&(window.location.hash="#"+encodeURIComponent(btoa(N))),f?.()},onToggle:p=>{H(p),d?.(p)}}),[e,a,r]),Y=J(({data:{from:p,type:N}})=>{N==="start"&&p!==m&&V()}),S=t.useCallback(async(p=!0)=>{await R(E,p),Y({type:"start",from:m})},[R,E]),j=t.useRef();return t.useEffect(()=>{!j.current&&s&&E&&(j.current=!0,S())},[S,s,E]),t.useEffect(()=>()=>{h.stop()},[h]),{id:m,canvasId:g,code:E,setCode:M,error:v||C,schedulerError:v,scheduler:h,evalError:C,evaluate:R,activateCode:S,activeCode:_,isDirty:b,pattern:P,started:F,start:Q,stop:V,pause:X,togglePlay:async()=>{F?h.pause():await S()}}}function de(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function O({type:e}){return l.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:l.default.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:l.default.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:l.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"})}[e])}const fe="_container_3i85k_1",ge="_header_3i85k_5",me="_buttons_3i85k_9",he="_button_3i85k_9",pe="_buttonDisabled_3i85k_17",be="_error_3i85k_21",ve="_body_3i85k_25",y={container:fe,header:ge,buttons:me,button:he,buttonDisabled:pe,error:be,body:ve},we=()=>K.getAudioContext().currentTime;function Ee({tune:e,hideOutsideView:a=!1,enableKeyboard:r,withCanvas:s=!1,canvasHeight:n=200}){const{code:i,setCode:u,evaluate:f,activateCode:o,error:d,isDirty:g,activeCode:m,pattern:v,started:k,scheduler:C,togglePlay:q,stop:E,canvasId:M}=G({initialCode:e,defaultOutput:K.webaudioOutput,getTime:we});ue({pattern:v,started:s&&k,getTime:()=>C.now(),onDraw:(b,h)=>{const R=document.querySelector("#"+M).getContext("2d");B.pianoroll({ctx:R,time:b,haps:h,autorange:1,fold:1,playhead:1})}});const[_,T]=t.useState(),[P,D]=re.useInView({threshold:.01}),F=t.useRef(),H=t.useMemo(()=>((D||!a)&&(F.current=!0),D||F.current),[D,a]);return $({view:_,pattern:v,active:k&&!m?.includes("strudel disable-highlighting"),getTime:()=>C.getPhase()}),t.useLayoutEffect(()=>{if(r){const b=async h=>{(h.ctrlKey||h.altKey)&&(h.code==="Enter"?(h.preventDefault(),U(_),await o()):h.code==="Period"&&(E(),h.preventDefault()))};return window.addEventListener("keydown",b,!0),()=>window.removeEventListener("keydown",b,!0)}},[r,v,i,f,E,_]),l.default.createElement("div",{className:y.container,ref:P},l.default.createElement("div",{className:y.header},l.default.createElement("div",{className:y.buttons},l.default.createElement("button",{className:z(y.button,k?"sc-animate-pulse":""),onClick:()=>q()},l.default.createElement(O,{type:k?"pause":"play"})),l.default.createElement("button",{className:z(g?y.button:y.buttonDisabled),onClick:()=>o()},l.default.createElement(O,{type:"refresh"}))),d&&l.default.createElement("div",{className:y.error},d.message)),l.default.createElement("div",{className:y.body},H&&l.default.createElement(W,{value:i,onChange:u,onViewChanged:T})),s&&l.default.createElement("canvas",{id:M,className:"w-full pointer-events-none",height:n,ref:b=>{b&&b.width!==b.clientWidth&&(b.width=b.clientWidth)}}))}const ye=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=W;exports.MiniRepl=Ee;exports.cx=z;exports.flash=U;exports.useHighlighting=$;exports.useKeydown=ye;exports.usePostMessage=J;exports.useStrudel=G; +"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react"),ee=require("@uiw/react-codemirror"),E=require("@codemirror/view"),H=require("@codemirror/state"),te=require("@codemirror/lang-javascript"),i=require("@lezer/highlight"),re=require("@uiw/codemirror-themes"),B=require("@strudel.cycles/core"),W=require("@strudel.cycles/webaudio"),ae=require("react-hook-inview"),oe=require("@strudel.cycles/transpiler"),$=e=>e&&typeof e=="object"&&"default"in e?e:{default:e},u=$(t),ne=$(ee),se=re.createTheme({theme:"dark",settings:{background:"#222",foreground:"#75baff",caret:"#ffcc00",selection:"rgba(128, 203, 196, 0.5)",selectionMatch:"#036dd626",lineHighlight:"#00000050",gutterBackground:"transparent",gutterForeground:"#8a919966"},styles:[{tag:i.tags.keyword,color:"#c792ea"},{tag:i.tags.operator,color:"#89ddff"},{tag:i.tags.special(i.tags.variableName),color:"#eeffff"},{tag:i.tags.typeName,color:"#c3e88d"},{tag:i.tags.atom,color:"#f78c6c"},{tag:i.tags.number,color:"#c3e88d"},{tag:i.tags.definition(i.tags.variableName),color:"#82aaff"},{tag:i.tags.string,color:"#c3e88d"},{tag:i.tags.special(i.tags.string),color:"#c3e88d"},{tag:i.tags.comment,color:"#7d8799"},{tag:i.tags.variableName,color:"#c792ea"},{tag:i.tags.tagName,color:"#c3e88d"},{tag:i.tags.bracket,color:"#525154"},{tag:i.tags.meta,color:"#ffcb6b"},{tag:i.tags.attributeName,color:"#c792ea"},{tag:i.tags.propertyName,color:"#c792ea"},{tag:i.tags.className,color:"#decb6b"},{tag:i.tags.invalid,color:"#ffffff"}]});const j=H.StateEffect.define(),ce=H.StateField.define({create(){return E.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(j))if(r.value){const s=E.Decoration.mark({attributes:{style:"background-color: #FFCA2880"}});e=E.Decoration.set([s.range(0,a.newDoc.length)])}else e=E.Decoration.set([]);return e}catch(r){return console.warn("flash error",r),e}},provide:e=>E.EditorView.decorations.from(e)}),J=e=>{e.dispatch({effects:j.of(!0)}),setTimeout(()=>{e.dispatch({effects:j.of(!1)})},200)},P=H.StateEffect.define(),ie=H.StateField.define({create(){return E.Decoration.none},update(e,a){try{for(let r of a.effects)if(r.is(P)){const s=r.value.map(n=>(n.context.locations||[]).map(({start:l,end:d})=>{const m=n.context.color||"#FFCA28";let o=a.newDoc.line(l.line).from+l.column,f=a.newDoc.line(d.line).from+d.column;const b=a.newDoc.length;return o>b||f>b?void 0:E.Decoration.mark({attributes:{style:`outline: 1.5px solid ${m};`}}).range(o,f)})).flat().filter(Boolean)||[];e=E.Decoration.set(s,!0)}return e}catch{return E.Decoration.set([])}},provide:e=>E.EditorView.decorations.from(e)}),le=[te.javascript(),se,ie,ce];function G({value:e,onChange:a,onViewChanged:r,onSelectionChange:s,options:n,editorDidMount:l}){const d=t.useCallback(f=>{a?.(f)},[a]),m=t.useCallback(f=>{r?.(f)},[r]),o=t.useCallback(f=>{f.selectionSet&&s&&s?.(f.state.selection)},[s]);return u.default.createElement(u.default.Fragment,null,u.default.createElement(ne.default,{value:e,onChange:d,onCreateEditor:m,onUpdate:o,extensions:le}))}function O(...e){return e.filter(Boolean).join(" ")}function Q({view:e,pattern:a,active:r,getTime:s}){const n=t.useRef([]),l=t.useRef();t.useEffect(()=>{if(e)if(a&&r){let d=requestAnimationFrame(function m(){try{const o=s(),b=[Math.max(l.current||o,o-1/10,0),o+1/60];l.current=b[1],n.current=n.current.filter(p=>p.whole.end>o);const v=a.queryArc(...b).filter(p=>p.hasOnset());n.current=n.current.concat(v),e.dispatch({effects:P.of(n.current)})}catch{e.dispatch({effects:P.of([])})}d=requestAnimationFrame(m)});return()=>{cancelAnimationFrame(d)}}else n.current=[],e.dispatch({effects:P.of([])})},[a,r,e])}function ue(e,a=!1){const r=t.useRef(),s=t.useRef(),n=m=>{if(s.current!==void 0){const o=m-s.current;e(m,o)}s.current=m,r.current=requestAnimationFrame(n)},l=()=>{r.current=requestAnimationFrame(n)},d=()=>{r.current&&cancelAnimationFrame(r.current),delete r.current};return t.useEffect(()=>{r.current&&(d(),l())},[e]),t.useEffect(()=>(a&&l(),d),[]),{start:l,stop:d}}function de({pattern:e,started:a,getTime:r,onDraw:s}){let n=t.useRef([]),l=t.useRef(null);const{start:d,stop:m}=ue(t.useCallback(()=>{const o=r();if(l.current===null){l.current=o;return}const f=e.queryArc(Math.max(l.current,o-1/10),o),b=4;l.current=o,n.current=(n.current||[]).filter(v=>v.whole.end>o-b).concat(f.filter(v=>v.hasOnset())),s(o,n.current)},[e]));t.useEffect(()=>{a?d():(n.current=[],m())},[a])}function X(e){return t.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),t.useCallback(a=>window.postMessage(a,"*"),[])}function Y({defaultOutput:e,interval:a,getTime:r,evalOnMount:s=!1,initialCode:n="",autolink:l=!1,beforeEval:d,afterEval:m,editPattern:o,onEvalError:f,onToggle:b,canvasId:v}){const p=t.useMemo(()=>fe(),[]);v=v||`canvas-${p}`;const[k,D]=t.useState(),[q,F]=t.useState(),[w,N]=t.useState(n),[C,T]=t.useState(),[z,R]=t.useState(),[S,V]=t.useState(!1),x=w!==C,{scheduler:M,evaluate:c,start:g,stop:_,pause:I}=t.useMemo(()=>B.repl({interval:a,defaultOutput:e,onSchedulerError:D,onEvalError:h=>{F(h),f?.(h)},getTime:r,transpiler:oe.transpiler,beforeEval:({code:h})=>{N(h),d?.()},editPattern:o?h=>o(h,p):void 0,afterEval:({pattern:h,code:L})=>{T(L),R(h),F(),D(),l&&(window.location.hash="#"+encodeURIComponent(btoa(L))),m?.()},onToggle:h=>{V(h),b?.(h)}}),[e,a,r]),Z=X(({data:{from:h,type:L}})=>{L==="start"&&h!==p&&_()}),A=t.useCallback(async(h=!0)=>{await c(w,h),Z({type:"start",from:p})},[c,w]),K=t.useRef();return t.useEffect(()=>{!K.current&&s&&w&&(K.current=!0,A())},[A,s,w]),t.useEffect(()=>()=>{M.stop()},[M]),{id:p,canvasId:v,code:w,setCode:N,error:k||q,schedulerError:k,scheduler:M,evalError:q,evaluate:c,activateCode:A,activeCode:C,isDirty:x,pattern:z,started:S,start:g,stop:_,pause:I,togglePlay:async()=>{S?M.pause():await A()}}}function fe(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}function U({type:e}){return u.default.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:u.default.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:u.default.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:u.default.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"})}[e])}const ge="_container_3i85k_1",me="_header_3i85k_5",he="_buttons_3i85k_9",pe="_button_3i85k_9",ve="_buttonDisabled_3i85k_17",be="_error_3i85k_21",Ee="_body_3i85k_25",y={container:ge,header:me,buttons:he,button:pe,buttonDisabled:ve,error:be,body:Ee},we=()=>W.getAudioContext().currentTime;function ye({tune:e,hideOutsideView:a=!1,enableKeyboard:r,withCanvas:s=!1,canvasHeight:n=200}){const{code:l,setCode:d,evaluate:m,activateCode:o,error:f,isDirty:b,activeCode:v,pattern:p,started:k,scheduler:D,togglePlay:q,stop:F,canvasId:w,id:N}=Y({initialCode:e,defaultOutput:W.webaudioOutput,getTime:we,editPattern:(c,g)=>c.withContext(_=>({..._,id:g}))});de({pattern:p,started:s&&k,getTime:()=>D.now(),onDraw:(c,g)=>{const _=document.querySelector("#"+w).getContext("2d");B.pianoroll({ctx:_,time:c,haps:g,autorange:1,fold:1,playhead:1})}});const[C,T]=t.useState(),[z,R]=ae.useInView({threshold:.01}),S=t.useRef(),V=t.useMemo(()=>((R||!a)&&(S.current=!0),R||S.current),[R,a]);Q({view:C,pattern:p,active:k&&!v?.includes("strudel disable-highlighting"),getTime:()=>D.getPhase()}),t.useLayoutEffect(()=>{if(r){const c=async g=>{(g.ctrlKey||g.altKey)&&(g.code==="Enter"?(g.preventDefault(),J(C),await o()):g.code==="Period"&&(F(),g.preventDefault()))};return window.addEventListener("keydown",c,!0),()=>window.removeEventListener("keydown",c,!0)}},[r,p,l,m,F,C]);const[x,M]=t.useState([]);return ke(t.useCallback(c=>{const{data:g}=c.detail;g?.hap?.context?.id===N&&M(I=>I.concat([c.detail]).slice(-10))},[])),u.default.createElement("div",{className:y.container,ref:z},u.default.createElement("div",{className:y.header},u.default.createElement("div",{className:y.buttons},u.default.createElement("button",{className:O(y.button,k?"sc-animate-pulse":""),onClick:()=>q()},u.default.createElement(U,{type:k?"pause":"play"})),u.default.createElement("button",{className:O(b?y.button:y.buttonDisabled),onClick:()=>o()},u.default.createElement(U,{type:"refresh"}))),f&&u.default.createElement("div",{className:y.error},f.message)),u.default.createElement("div",{className:y.body},V&&u.default.createElement(G,{value:l,onChange:d,onViewChanged:T})),s&&u.default.createElement("canvas",{id:w,className:"w-full pointer-events-none",height:n,ref:c=>{c&&c.width!==c.clientWidth&&(c.width=c.clientWidth)}}),!!x.length&&u.default.createElement("div",{className:"sc-bg-gray-800 sc-rounded-md sc-p-2"},x.map(({message:c},g)=>u.default.createElement("div",{key:g},c))))}function ke(e){_e(B.logger.key,e)}function _e(e,a,r=!1){t.useEffect(()=>(document.addEventListener(e,a,r),()=>{document.removeEventListener(e,a,r)}),[a])}const Ce=e=>t.useLayoutEffect(()=>(window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)),[e]);exports.CodeMirror=G;exports.MiniRepl=ye;exports.cx=O;exports.flash=J;exports.useHighlighting=Q;exports.useKeydown=Ce;exports.usePostMessage=X;exports.useStrudel=Y; diff --git a/packages/react/dist/index.es.js b/packages/react/dist/index.es.js index 6545e0a7..d0027424 100644 --- a/packages/react/dist/index.es.js +++ b/packages/react/dist/index.es.js @@ -1,15 +1,15 @@ -import i, { useCallback as N, useRef as E, useEffect as F, useMemo as V, useState as _, useLayoutEffect as U } from "react"; -import Y from "@uiw/react-codemirror"; -import { Decoration as y, EditorView as W } from "@codemirror/view"; -import { StateEffect as $, StateField as G } from "@codemirror/state"; -import { javascript as Z } from "@codemirror/lang-javascript"; +import l, { useCallback as N, useRef as k, useEffect as _, useMemo as K, useState as w, useLayoutEffect as G } from "react"; +import Z from "@uiw/react-codemirror"; +import { Decoration as y, EditorView as J } from "@codemirror/view"; +import { StateEffect as Q, StateField as X } from "@codemirror/state"; +import { javascript as ee } from "@codemirror/lang-javascript"; import { tags as s } from "@lezer/highlight"; -import { createTheme as ee } from "@uiw/codemirror-themes"; -import { repl as te, pianoroll as re } from "@strudel.cycles/core"; -import { webaudioOutput as oe, getAudioContext as ne } from "@strudel.cycles/webaudio"; -import { useInView as ae } from "react-hook-inview"; -import { transpiler as se } from "@strudel.cycles/transpiler"; -const ce = ee({ +import { createTheme as te } from "@uiw/codemirror-themes"; +import { repl as re, logger as ne, pianoroll as oe } from "@strudel.cycles/core"; +import { webaudioOutput as ae, getAudioContext as ce } from "@strudel.cycles/webaudio"; +import { useInView as se } from "react-hook-inview"; +import { transpiler as ie } from "@strudel.cycles/transpiler"; +const le = te({ theme: "dark", settings: { background: "#222", @@ -42,14 +42,14 @@ const ce = ee({ { tag: s.invalid, color: "#ffffff" } ] }); -const B = $.define(), ie = G.define({ +const j = Q.define(), ue = X.define({ create() { return y.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(B)) + if (t.is(j)) if (t.value) { const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } }); e = y.set([a.range(0, r.newDoc.length)]); @@ -60,25 +60,25 @@ const B = $.define(), ie = G.define({ return console.warn("flash error", t), e; } }, - provide: (e) => W.decorations.from(e) -}), le = (e) => { - e.dispatch({ effects: B.of(!0) }), setTimeout(() => { - e.dispatch({ effects: B.of(!1) }); + provide: (e) => J.decorations.from(e) +}), de = (e) => { + e.dispatch({ effects: j.of(!0) }), setTimeout(() => { + e.dispatch({ effects: j.of(!1) }); }, 200); -}, H = $.define(), ue = G.define({ +}, z = Q.define(), fe = X.define({ create() { return y.none; }, update(e, r) { try { for (let t of r.effects) - if (t.is(H)) { + if (t.is(z)) { const a = t.value.map( - (n) => (n.context.locations || []).map(({ start: c, end: l }) => { - const d = n.context.color || "#FFCA28"; - let o = r.newDoc.line(c.line).from + c.column, u = r.newDoc.line(l.line).from + l.column; - const f = r.newDoc.length; - return o > f || u > f ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(o, u); + (o) => (o.context.locations || []).map(({ start: i, end: u }) => { + const m = o.context.color || "#FFCA28"; + let n = r.newDoc.line(i.line).from + i.column, d = r.newDoc.line(u.line).from + u.column; + const v = r.newDoc.length; + return n > v || d > v ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${m};` } }).range(n, d); }) ).flat().filter(Boolean) || []; e = y.set(a, !0); @@ -88,291 +88,314 @@ const B = $.define(), ie = G.define({ return y.set([]); } }, - provide: (e) => W.decorations.from(e) -}), de = [Z(), ce, ue, ie]; -function fe({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: n, editorDidMount: c }) { - const l = N( - (u) => { - r?.(u); + provide: (e) => J.decorations.from(e) +}), me = [ee(), le, fe, ue]; +function ge({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: o, editorDidMount: i }) { + const u = N( + (d) => { + r?.(d); }, [r] - ), d = N( - (u) => { - t?.(u); + ), m = N( + (d) => { + t?.(d); }, [t] - ), o = N( - (u) => { - u.selectionSet && a && a?.(u.state.selection); + ), n = N( + (d) => { + d.selectionSet && a && a?.(d.state.selection); }, [a] ); - return /* @__PURE__ */ i.createElement(i.Fragment, null, /* @__PURE__ */ i.createElement(Y, { + return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(Z, { value: e, - onChange: l, - onCreateEditor: d, - onUpdate: o, - extensions: de + onChange: u, + onCreateEditor: m, + onUpdate: n, + extensions: me })); } -function j(...e) { +function W(...e) { return e.filter(Boolean).join(" "); } -function me({ view: e, pattern: r, active: t, getTime: a }) { - const n = E([]), c = E(); - F(() => { +function pe({ view: e, pattern: r, active: t, getTime: a }) { + const o = k([]), i = k(); + _(() => { if (e) if (r && t) { - let l = requestAnimationFrame(function d() { + let u = requestAnimationFrame(function m() { try { - const o = a(), f = [Math.max(c.current || o, o - 1 / 10, 0), o + 1 / 60]; - c.current = f[1], n.current = n.current.filter((v) => v.whole.end > o); - const m = r.queryArc(...f).filter((v) => v.hasOnset()); - n.current = n.current.concat(m), e.dispatch({ effects: H.of(n.current) }); + const n = a(), v = [Math.max(i.current || n, n - 1 / 10, 0), n + 1 / 60]; + i.current = v[1], o.current = o.current.filter((p) => p.whole.end > n); + const h = r.queryArc(...v).filter((p) => p.hasOnset()); + o.current = o.current.concat(h), e.dispatch({ effects: z.of(o.current) }); } catch { - e.dispatch({ effects: H.of([]) }); + e.dispatch({ effects: z.of([]) }); } - l = requestAnimationFrame(d); + u = requestAnimationFrame(m); }); return () => { - cancelAnimationFrame(l); + cancelAnimationFrame(u); }; } else - n.current = [], e.dispatch({ effects: H.of([]) }); + o.current = [], e.dispatch({ effects: z.of([]) }); }, [r, t, e]); } -function ge(e, r = !1) { - const t = E(), a = E(), n = (d) => { +function he(e, r = !1) { + const t = k(), a = k(), o = (m) => { if (a.current !== void 0) { - const o = d - a.current; - e(d, o); + const n = m - a.current; + e(m, n); } - a.current = d, t.current = requestAnimationFrame(n); - }, c = () => { - t.current = requestAnimationFrame(n); - }, l = () => { + a.current = m, t.current = requestAnimationFrame(o); + }, i = () => { + t.current = requestAnimationFrame(o); + }, u = () => { t.current && cancelAnimationFrame(t.current), delete t.current; }; - return F(() => { - t.current && (l(), c()); - }, [e]), F(() => (r && c(), l), []), { - start: c, - stop: l + return _(() => { + t.current && (u(), i()); + }, [e]), _(() => (r && i(), u), []), { + start: i, + stop: u }; } -function pe({ pattern: e, started: r, getTime: t, onDraw: a }) { - let n = E([]), c = E(null); - const { start: l, stop: d } = ge( +function ve({ pattern: e, started: r, getTime: t, onDraw: a }) { + let o = k([]), i = k(null); + const { start: u, stop: m } = he( N(() => { - const o = t(); - if (c.current === null) { - c.current = o; + const n = t(); + if (i.current === null) { + i.current = n; return; } - const u = e.queryArc(Math.max(c.current, o - 1 / 10), o), f = 4; - c.current = o, n.current = (n.current || []).filter((m) => m.whole.end > o - f).concat(u.filter((m) => m.hasOnset())), a(o, n.current); + const d = e.queryArc(Math.max(i.current, n - 1 / 10), n), v = 4; + i.current = n, o.current = (o.current || []).filter((h) => h.whole.end > n - v).concat(d.filter((h) => h.hasOnset())), a(n, o.current); }, [e]) ); - F(() => { - r ? l() : (n.current = [], d()); + _(() => { + r ? u() : (o.current = [], m()); }, [r]); } -function he(e) { - return F(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []); +function be(e) { + return _(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []); } -function ve({ +function Ee({ defaultOutput: e, interval: r, getTime: t, evalOnMount: a = !1, - initialCode: n = "", - autolink: c = !1, - beforeEval: l, - afterEval: d, - onEvalError: o, - onToggle: u, - canvasId: f + initialCode: o = "", + autolink: i = !1, + beforeEval: u, + afterEval: m, + editPattern: n, + onEvalError: d, + onToggle: v, + canvasId: h }) { - const m = V(() => be(), []); - f = f || `canvas-${m}`; - const [v, k] = _(), [M, T] = _(), [b, A] = _(n), [C, S] = _(), [z, D] = _(), [x, L] = _(!1), h = b !== C, { scheduler: g, evaluate: R, start: J, stop: O, pause: Q } = V( - () => te({ + const p = K(() => we(), []); + h = h || `canvas-${p}`; + const [F, A] = w(), [P, D] = w(), [b, q] = w(o), [x, V] = w(), [I, R] = w(), [L, B] = w(!1), H = b !== x, { scheduler: M, evaluate: c, start: f, stop: C, pause: O } = K( + () => re({ interval: r, defaultOutput: e, - onSchedulerError: k, - onEvalError: (p) => { - T(p), o?.(p); + onSchedulerError: A, + onEvalError: (g) => { + D(g), d?.(g); }, getTime: t, - transpiler: se, - beforeEval: ({ code: p }) => { - A(p), l?.(); + transpiler: ie, + beforeEval: ({ code: g }) => { + q(g), u?.(); }, - afterEval: ({ pattern: p, code: q }) => { - S(q), D(p), T(), k(), c && (window.location.hash = "#" + encodeURIComponent(btoa(q))), d?.(); + editPattern: n ? (g) => n(g, p) : void 0, + afterEval: ({ pattern: g, code: T }) => { + V(T), R(g), D(), A(), i && (window.location.hash = "#" + encodeURIComponent(btoa(T))), m?.(); }, - onToggle: (p) => { - L(p), u?.(p); + onToggle: (g) => { + B(g), v?.(g); } }), [e, r, t] - ), X = he(({ data: { from: p, type: q } }) => { - q === "start" && p !== m && O(); - }), P = N( - async (p = !0) => { - await R(b, p), X({ type: "start", from: m }); + ), Y = be(({ data: { from: g, type: T } }) => { + T === "start" && g !== p && C(); + }), S = N( + async (g = !0) => { + await c(b, g), Y({ type: "start", from: p }); }, - [R, b] - ), K = E(); - return F(() => { - !K.current && a && b && (K.current = !0, P()); - }, [P, a, b]), F(() => () => { - g.stop(); - }, [g]), { - id: m, - canvasId: f, + [c, b] + ), U = k(); + return _(() => { + !U.current && a && b && (U.current = !0, S()); + }, [S, a, b]), _(() => () => { + M.stop(); + }, [M]), { + id: p, + canvasId: h, code: b, - setCode: A, - error: v || M, - schedulerError: v, - scheduler: g, - evalError: M, - evaluate: R, - activateCode: P, - activeCode: C, - isDirty: h, - pattern: z, - started: x, - start: J, - stop: O, - pause: Q, + setCode: q, + error: F || P, + schedulerError: F, + scheduler: M, + evalError: P, + evaluate: c, + activateCode: S, + activeCode: x, + isDirty: H, + pattern: I, + started: L, + start: f, + stop: C, + pause: O, togglePlay: async () => { - x ? g.pause() : await P(); + L ? M.pause() : await S(); } }; } -function be() { +function we() { return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); } -function I({ type: e }) { - return /* @__PURE__ */ i.createElement("svg", { +function $({ type: e }) { + return /* @__PURE__ */ l.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "sc-h-5 sc-w-5", viewBox: "0 0 20 20", fill: "currentColor" }, { - refresh: /* @__PURE__ */ i.createElement("path", { + refresh: /* @__PURE__ */ l.createElement("path", { fillRule: "evenodd", d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z", clipRule: "evenodd" }), - play: /* @__PURE__ */ i.createElement("path", { + play: /* @__PURE__ */ l.createElement("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z", clipRule: "evenodd" }), - pause: /* @__PURE__ */ i.createElement("path", { + pause: /* @__PURE__ */ l.createElement("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z", clipRule: "evenodd" }) }[e]); } -const we = "_container_3i85k_1", ye = "_header_3i85k_5", Ee = "_buttons_3i85k_9", ke = "_button_3i85k_9", _e = "_buttonDisabled_3i85k_17", Fe = "_error_3i85k_21", Ce = "_body_3i85k_25", w = { - container: we, - header: ye, - buttons: Ee, - button: ke, - buttonDisabled: _e, - error: Fe, - body: Ce -}, Ne = () => ne().currentTime; -function Be({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: n = 200 }) { +const ye = "_container_3i85k_1", ke = "_header_3i85k_5", _e = "_buttons_3i85k_9", Fe = "_button_3i85k_9", Ce = "_buttonDisabled_3i85k_17", Ne = "_error_3i85k_21", xe = "_body_3i85k_25", E = { + container: ye, + header: ke, + buttons: _e, + button: Fe, + buttonDisabled: Ce, + error: Ne, + body: xe +}, Me = () => ce().currentTime; +function je({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: o = 200 }) { const { - code: c, - setCode: l, - evaluate: d, - activateCode: o, - error: u, - isDirty: f, - activeCode: m, - pattern: v, - started: k, - scheduler: M, - togglePlay: T, - stop: b, - canvasId: A - } = ve({ + code: i, + setCode: u, + evaluate: m, + activateCode: n, + error: d, + isDirty: v, + activeCode: h, + pattern: p, + started: F, + scheduler: A, + togglePlay: P, + stop: D, + canvasId: b, + id: q + } = Ee({ initialCode: e, - defaultOutput: oe, - getTime: Ne + defaultOutput: ae, + getTime: Me, + editPattern: (c, f) => c.withContext((C) => ({ ...C, id: f })) }); - pe({ - pattern: v, - started: a && k, - getTime: () => M.now(), - onDraw: (h, g) => { - const R = document.querySelector("#" + A).getContext("2d"); - re({ ctx: R, time: h, haps: g, autorange: 1, fold: 1, playhead: 1 }); + ve({ + pattern: p, + started: a && F, + getTime: () => A.now(), + onDraw: (c, f) => { + const C = document.querySelector("#" + b).getContext("2d"); + oe({ ctx: C, time: c, haps: f, autorange: 1, fold: 1, playhead: 1 }); } }); - const [C, S] = _(), [z, D] = ae({ + const [x, V] = w(), [I, R] = se({ threshold: 0.01 - }), x = E(), L = V(() => ((D || !r) && (x.current = !0), D || x.current), [D, r]); - return me({ - view: C, - pattern: v, - active: k && !m?.includes("strudel disable-highlighting"), - getTime: () => M.getPhase() - }), U(() => { + }), L = k(), B = K(() => ((R || !r) && (L.current = !0), R || L.current), [R, r]); + pe({ + view: x, + pattern: p, + active: F && !h?.includes("strudel disable-highlighting"), + getTime: () => A.getPhase() + }), G(() => { if (t) { - const h = async (g) => { - (g.ctrlKey || g.altKey) && (g.code === "Enter" ? (g.preventDefault(), le(C), await o()) : g.code === "Period" && (b(), g.preventDefault())); + const c = async (f) => { + (f.ctrlKey || f.altKey) && (f.code === "Enter" ? (f.preventDefault(), de(x), await n()) : f.code === "Period" && (D(), f.preventDefault())); }; - return window.addEventListener("keydown", h, !0), () => window.removeEventListener("keydown", h, !0); + return window.addEventListener("keydown", c, !0), () => window.removeEventListener("keydown", c, !0); } - }, [t, v, c, d, b, C]), /* @__PURE__ */ i.createElement("div", { - className: w.container, - ref: z - }, /* @__PURE__ */ i.createElement("div", { - className: w.header - }, /* @__PURE__ */ i.createElement("div", { - className: w.buttons - }, /* @__PURE__ */ i.createElement("button", { - className: j(w.button, k ? "sc-animate-pulse" : ""), - onClick: () => T() - }, /* @__PURE__ */ i.createElement(I, { - type: k ? "pause" : "play" - })), /* @__PURE__ */ i.createElement("button", { - className: j(f ? w.button : w.buttonDisabled), - onClick: () => o() - }, /* @__PURE__ */ i.createElement(I, { + }, [t, p, i, m, D, x]); + const [H, M] = w([]); + return Ae( + N((c) => { + const { data: f } = c.detail; + f?.hap?.context?.id === q && M((O) => O.concat([c.detail]).slice(-10)); + }, []) + ), /* @__PURE__ */ l.createElement("div", { + className: E.container, + ref: I + }, /* @__PURE__ */ l.createElement("div", { + className: E.header + }, /* @__PURE__ */ l.createElement("div", { + className: E.buttons + }, /* @__PURE__ */ l.createElement("button", { + className: W(E.button, F ? "sc-animate-pulse" : ""), + onClick: () => P() + }, /* @__PURE__ */ l.createElement($, { + type: F ? "pause" : "play" + })), /* @__PURE__ */ l.createElement("button", { + className: W(v ? E.button : E.buttonDisabled), + onClick: () => n() + }, /* @__PURE__ */ l.createElement($, { type: "refresh" - }))), u && /* @__PURE__ */ i.createElement("div", { - className: w.error - }, u.message)), /* @__PURE__ */ i.createElement("div", { - className: w.body - }, L && /* @__PURE__ */ i.createElement(fe, { - value: c, - onChange: l, - onViewChanged: S - })), a && /* @__PURE__ */ i.createElement("canvas", { - id: A, + }))), d && /* @__PURE__ */ l.createElement("div", { + className: E.error + }, d.message)), /* @__PURE__ */ l.createElement("div", { + className: E.body + }, B && /* @__PURE__ */ l.createElement(ge, { + value: i, + onChange: u, + onViewChanged: V + })), a && /* @__PURE__ */ l.createElement("canvas", { + id: b, className: "w-full pointer-events-none", - height: n, - ref: (h) => { - h && h.width !== h.clientWidth && (h.width = h.clientWidth); + height: o, + ref: (c) => { + c && c.width !== c.clientWidth && (c.width = c.clientWidth); } - })); + }), !!H.length && /* @__PURE__ */ l.createElement("div", { + className: "sc-bg-gray-800 sc-rounded-md sc-p-2" + }, H.map(({ message: c }, f) => /* @__PURE__ */ l.createElement("div", { + key: f + }, c)))); } -const Oe = (e) => U(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); +function Ae(e) { + De(ne.key, e); +} +function De(e, r, t = !1) { + _(() => (document.addEventListener(e, r, t), () => { + document.removeEventListener(e, r, t); + }), [r]); +} +const Ue = (e) => G(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]); export { - fe as CodeMirror, - Be as MiniRepl, - j as cx, - le as flash, - me as useHighlighting, - Oe as useKeydown, - he as usePostMessage, - ve as useStrudel + ge as CodeMirror, + je as MiniRepl, + W as cx, + de as flash, + pe as useHighlighting, + Ue as useKeydown, + be as usePostMessage, + Ee as useStrudel }; diff --git a/packages/react/dist/style.css b/packages/react/dist/style.css index b28aca62..6e937139 100644 --- a/packages/react/dist/style.css +++ b/packages/react/dist/style.css @@ -1 +1 @@ -.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto} +.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.sc-rounded-md{border-radius:.375rem}.sc-bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.sc-p-2{padding:.5rem}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto} diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index a7874e55..70ebe22c 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -1,6 +1,6 @@ import { pianoroll } from '@strudel.cycles/core'; import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; -import React, { useLayoutEffect, useMemo, useRef, useState } from 'react'; +import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react'; import { useInView } from 'react-hook-inview'; import 'tailwindcss/tailwind.css'; import cx from '../cx'; @@ -11,6 +11,7 @@ import CodeMirror6, { flash } from './CodeMirror6'; import { Icon } from './Icon'; import styles from './MiniRepl.module.css'; import './style.css'; +import { logger } from '@strudel.cycles/core'; const getTime = () => getAudioContext().currentTime; @@ -29,10 +30,14 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa togglePlay, stop, canvasId, + id: replId, } = useStrudel({ initialCode: tune, defaultOutput: webaudioOutput, getTime, + editPattern: (pat, id) => { + return pat.withContext((ctx) => ({ ...ctx, id })); + }, }); usePatternFrame({ @@ -86,6 +91,20 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa } }, [enableKeyboard, pattern, code, evaluate, stop, view]); + const [log, setLog] = useState([]); + useLogger( + useCallback((e) => { + const { data } = e.detail; + const logId = data?.hap?.context?.id; + // const logId = data?.pattern?.meta?.id; + if (logId === replId) { + setLog((l) => { + return l.concat([e.detail]).slice(-10); + }); + } + }, []), + ); + return (
    @@ -114,6 +133,28 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa }} > )} + {!!log.length && ( +
    + {log.map(({ message }, i) => ( +
    {message}
    + ))} +
    + )}
    ); } + +// TODO: dedupe +function useLogger(onTrigger) { + useEvent(logger.key, onTrigger); +} + +// TODO: dedupe +function useEvent(name, onTrigger, useCapture = false) { + useEffect(() => { + document.addEventListener(name, onTrigger, useCapture); + return () => { + document.removeEventListener(name, onTrigger, useCapture); + }; + }, [onTrigger]); +} diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index 5d475e1e..6308cf5e 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -12,6 +12,7 @@ function useStrudel({ autolink = false, beforeEval, afterEval, + editPattern, onEvalError, onToggle, canvasId, @@ -44,6 +45,7 @@ function useStrudel({ setCode(code); beforeEval?.(); }, + editPattern: editPattern ? (pat) => editPattern(pat, id) : undefined, afterEval: ({ pattern: _pattern, code }) => { setActiveCode(code); setPattern(_pattern); diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 11eb170b..f89d3d3a 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -195,9 +195,14 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => { hap.value = { note: hap.value }; } */ if (typeof hap.value !== 'object') { - throw new Error( - `hap.value ${hap.value} is not supported by webaudio output. Hint: append .note() or .s() to the end`, + logger( + `hap.value "${hap.value}" is not supported by webaudio output. Hint: append .note() or .s() to the end`, + 'error', ); + /* throw new Error( + `hap.value "${hap.value}"" is not supported by webaudio output. Hint: append .note() or .s() to the end`, + ); */ + return; } // calculate correct time (tone.js workaround) let t = ac.currentTime + deadline; From b695e090cd5f0f07795b9f41ad0f4a8d6943bc29 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Jan 2023 23:27:30 +0100 Subject: [PATCH 10/11] docs: control params --- website/src/config.ts | 2 +- .../src/pages/functions/value-modifiers.mdx | 137 +++++++++++++++--- website/src/pages/learn/factories.mdx | 5 + website/src/pages/learn/signals.mdx | 26 ++-- 4 files changed, 134 insertions(+), 36 deletions(-) diff --git a/website/src/config.ts b/website/src/config.ts index 3d5c6069..c6cc9f59 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -59,7 +59,7 @@ export const SIDEBAR: Sidebar = { { text: 'Introduction', link: 'functions/intro' }, { text: 'Pattern Constructors', link: 'learn/factories' }, { text: 'Time Modifiers', link: 'learn/time-modifiers' }, - { text: 'Value Modifiers', link: 'functions/value-modifiers' }, + { text: 'Control Parameters', link: 'functions/value-modifiers' }, { text: 'Signals', link: 'learn/signals' }, { text: 'Conditional Modifiers', link: 'learn/conditional-modifiers' }, { text: 'Tonal Modifiers', link: 'learn/tonal' }, diff --git a/website/src/pages/functions/value-modifiers.mdx b/website/src/pages/functions/value-modifiers.mdx index 1f949b01..308dfd77 100644 --- a/website/src/pages/functions/value-modifiers.mdx +++ b/website/src/pages/functions/value-modifiers.mdx @@ -1,61 +1,154 @@ --- -title: Value Modifiers +title: Control Parameters layout: ../../layouts/MainLayout.astro --- import { MiniRepl } from '../../docs/MiniRepl'; import { JsDoc } from '../../docs/JsDoc'; -# Value Modifiers +# Control Parameters -## Notes +Besides functions that control time, we saw earlier that functions like `note` and `cutoff` control different parameters (short params) of an event. +Let's now look more closely at how these `param(eter) functions` work. -Notes are automatically available as variables: +# Parameter Functions - +A very powerful feature of tidal patterns is that each parameter can be controlled independently: -An important difference to the mini notation: -For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name. +") +.gain(.8) +.s('sawtooth') +.log()`} +/> -The above is the same as: +In this example, the parameters `note`, `cutoff`, `gain` and `s` are controlled independently by either patterns or plain values (numbers / text). +After pressing play, we can observe the time and parameter values of each event (hap) in the output created by `.log()`. - +## Plain vs Parameterized Values -Using strings, you can also use "#". +Patterns that are not wrapped inside a param function will contain unlabeled `plain values`: -## Alternative Syntax +".log()`} /> -In the above example, we are nesting a function inside a function, which makes reading the parens a little more difficult. -To avoid getting to many nested parens, there is an alternative syntax to add a type to a pattern: +This will not generate any sound output, because Strudel could only guess which param is meant by these letters. - +Now compare that to the version wrapped in `note`: + +").log()`} /> + +Now it is clear that these letters are meant to be played as notes. +Under the hood, the `note` function (as well as all other param functions) +will wrap each plain value in an object. If the note function did not exist, we would need to write: + + + +This will have the same output, though it is rather unwieldy to read and write. + +## Wrapping Parameter Functions + +To avoid too much nesting, param functions can also be chained like this: + + + +This is equivalent to `note(cat('c','e','g')).log()`. You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty! -## Pattern.add +## Plain Value Modification + +Patterns of plain values can be modified with any of the following operators: + +").log()`} /> + +Here, the add function modifies the numbers on the left. +Again, there is no output because these numbers have no meaning without a param. + +## Param Value Modification + +To modify a parameter value, you can either: + +- Use the operator on the plain value pattern, inside the param function: + + ")).room(.1).log()`} /> + +- Similarly, use the operator on the plain value pattern and wrap it later: + + ").note().room(.1).log()`} /> + +- Specify which param should be modified inside the operator function: + + ")).log()`} /> + +- Modify _all_ numeral params: + + ").log()`} /> + +Which of these 3 ways to use strongly depends on the context! +Note that the order of chaining param functions also matters! +In the last example, the `room` value would not have changed if it was applied later: + +").room(.1).log()`} /> + +This shows how the execution of the chained functions goes from left to right. +In this case, the `.add` will only modify what's on the left side. + +# Operators + +This group of functions allows to modify the value of events. + +## add -## Pattern.sub +## sub -## Pattern.mul +## mul -## Pattern.div +## div -## Pattern.round +## round -## Pattern.apply +## floor - + -## Pattern.range +## ceil + + + +## range + +# Custom Parameters + +You can also create your own parameters: + + + +Multiple params can also be created in a more consice way, using `createParams`: + + + +Note that these params will not do anything until you give them meaning in your custom output! diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx index 7bce19df..d98629d2 100644 --- a/website/src/pages/learn/factories.mdx +++ b/website/src/pages/learn/factories.mdx @@ -20,6 +20,7 @@ These are the equivalents used by the Mini Notation: | `timeCat([3,x],[2,y])` | `"x@2 y@2"` | | `polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` | | `polymeterSteps(2, x, y, z)` | `"{x y z}%2"` | +| `silence` | `"~"` | ## cat @@ -56,3 +57,7 @@ As a chained function: ## polymeterSteps + +## silence + + diff --git a/website/src/pages/learn/signals.mdx b/website/src/pages/learn/signals.mdx index ee5614d1..0cf0f58e 100644 --- a/website/src/pages/learn/signals.mdx +++ b/website/src/pages/learn/signals.mdx @@ -56,54 +56,54 @@ These methods add random behavior to your Patterns. -## Pattern.degradeBy +## degradeBy -## Pattern.degrade +## degrade -## Pattern.undegradeBy +## undegradeBy -## Pattern.sometimesBy +## sometimesBy -## Pattern.sometimes +## sometimes -## Pattern.someCyclesBy +## someCyclesBy -## Pattern.someCycles +## someCycles -## Pattern.often +## often -## Pattern.rarely +## rarely -## Pattern.almostNever +## almostNever -## Pattern.almostAlways +## almostAlways -## Pattern.never +## never -## Pattern.always +## always From 57ba353594001b92c5d7b6ea241c9de4c432656c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 9 Jan 2023 23:31:30 +0100 Subject: [PATCH 11/11] snaps --- test/__snapshots__/examples.test.mjs.snap | 90 +++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 8f9fe9f2..e5a2ca32 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -920,6 +920,27 @@ exports[`runs examples > example "cat" example index 0 2`] = ` ] `; +exports[`runs examples > example "ceil" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:42 ]", + "[ 1/4 → 1/2 | note:43 ]", + "[ 1/2 → 3/4 | note:43 ]", + "[ 3/4 → 1/1 | note:43 ]", + "[ 1/1 → 5/4 | note:42 ]", + "[ 5/4 → 3/2 | note:43 ]", + "[ 3/2 → 7/4 | note:43 ]", + "[ 7/4 → 2/1 | note:43 ]", + "[ 2/1 → 9/4 | note:42 ]", + "[ 9/4 → 5/2 | note:43 ]", + "[ 5/2 → 11/4 | note:43 ]", + "[ 11/4 → 3/1 | note:43 ]", + "[ 3/1 → 13/4 | note:42 ]", + "[ 13/4 → 7/2 | note:43 ]", + "[ 7/2 → 15/4 | note:43 ]", + "[ 15/4 → 4/1 | note:43 ]", +] +`; + exports[`runs examples > example "chooseCycles" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:bd ]", @@ -1680,6 +1701,27 @@ exports[`runs examples > example "firstOf" example index 0 1`] = ` ] `; +exports[`runs examples > example "floor" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:42 ]", + "[ 1/4 → 1/2 | note:42 ]", + "[ 1/2 → 3/4 | note:42 ]", + "[ 3/4 → 1/1 | note:43 ]", + "[ 1/1 → 5/4 | note:42 ]", + "[ 5/4 → 3/2 | note:42 ]", + "[ 3/2 → 7/4 | note:42 ]", + "[ 7/4 → 2/1 | note:43 ]", + "[ 2/1 → 9/4 | note:42 ]", + "[ 9/4 → 5/2 | note:42 ]", + "[ 5/2 → 11/4 | note:42 ]", + "[ 11/4 → 3/1 | note:43 ]", + "[ 3/1 → 13/4 | note:42 ]", + "[ 13/4 → 7/2 | note:42 ]", + "[ 7/2 → 15/4 | note:42 ]", + "[ 15/4 → 4/1 | note:43 ]", +] +`; + exports[`runs examples > example "freq" example index 0 1`] = ` [ "[ 0/1 → 1/4 | freq:220 s:superzow ]", @@ -2312,6 +2354,52 @@ exports[`runs examples > example "perlin" example index 0 1`] = ` ] `; +exports[`runs examples > example "polymeter" example index 0 1`] = ` +[ + "[ 0/1 → 1/3 | note:c ]", + "[ 1/3 → 2/3 | note:eb ]", + "[ 2/3 → 1/1 | note:g ]", + "[ 1/1 → 4/3 | note:c ]", + "[ 4/3 → 5/3 | note:eb ]", + "[ 5/3 → 2/1 | note:g ]", + "[ 2/1 → 7/3 | note:c ]", + "[ 7/3 → 8/3 | note:eb ]", + "[ 8/3 → 3/1 | note:g ]", + "[ 3/1 → 10/3 | note:c ]", + "[ 10/3 → 11/3 | note:eb ]", + "[ 11/3 → 4/1 | note:g ]", + "[ 0/1 → 1/3 | note:c2 ]", + "[ 1/3 → 2/3 | note:g2 ]", + "[ 2/3 → 1/1 | note:c2 ]", + "[ 1/1 → 4/3 | note:g2 ]", + "[ 4/3 → 5/3 | note:c2 ]", + "[ 5/3 → 2/1 | note:g2 ]", + "[ 2/1 → 7/3 | note:c2 ]", + "[ 7/3 → 8/3 | note:g2 ]", + "[ 8/3 → 3/1 | note:c2 ]", + "[ 3/1 → 10/3 | note:g2 ]", + "[ 10/3 → 11/3 | note:c2 ]", + "[ 11/3 → 4/1 | note:g2 ]", +] +`; + +exports[`runs examples > example "polymeterSteps" example index 0 1`] = ` +[ + "[ 0/1 → 1/2 | note:c ]", + "[ 1/2 → 1/1 | note:d ]", + "[ 1/1 → 3/2 | note:e ]", + "[ 3/2 → 2/1 | note:f ]", + "[ 2/1 → 5/2 | note:g ]", + "[ 5/2 → 3/1 | note:f ]", + "[ 3/1 → 7/2 | note:e ]", + "[ 7/2 → 4/1 | note:d ]", + "[ 0/1 → 1/1 | s:bd ]", + "[ 1/1 → 2/1 | s:bd ]", + "[ 2/1 → 3/1 | s:bd ]", + "[ 3/1 → 4/1 | s:bd ]", +] +`; + exports[`runs examples > example "pure" example index 0 1`] = ` [ "[ 0/1 → 1/1 | e4 ]", @@ -2733,6 +2821,8 @@ exports[`runs examples > example "shape" example index 0 1`] = ` ] `; +exports[`runs examples > example "silence" example index 0 1`] = `[]`; + exports[`runs examples > example "sine" example index 0 1`] = ` [ "[ 0/1 → 1/8 | note:Eb4 ]",