├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ ├── deploy.yml │ └── test.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bench └── tunes.bench.mjs ├── eslint.config.mjs ├── examples ├── README.md ├── buildless │ ├── README.md │ ├── basic.html │ ├── canvas.html │ ├── headless-simple.html │ ├── headless-with-samples.html │ ├── hs2js.html │ ├── minimal-repl.html │ ├── tidal.html │ ├── web-component-iframe.html │ ├── web-component-no-iframe.html │ └── web.html ├── codemirror-repl │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ ├── style.css │ └── tunes.mjs ├── headless-repl │ ├── .gitignore │ ├── README.md │ ├── index.html │ └── package.json ├── minimal-repl │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ └── tune.mjs ├── superdough │ ├── .gitignore │ ├── README.md │ ├── index.html │ └── package.json └── tidal-repl │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.js │ ├── package.json │ └── public │ └── README.md ├── index.mjs ├── jsdoc ├── jsdoc-synonyms.js ├── jsdoc.config.json └── undocumented.mjs ├── lerna.json ├── my-patterns └── README.md ├── package.json ├── packages ├── README.md ├── codemirror │ ├── README.md │ ├── autocomplete.mjs │ ├── codemirror.mjs │ ├── flash.mjs │ ├── highlight.mjs │ ├── html.mjs │ ├── index.mjs │ ├── keybindings.mjs │ ├── package.json │ ├── slider.mjs │ ├── themes.mjs │ ├── themes │ │ ├── CutiePi.mjs │ │ ├── algoboy.mjs │ │ ├── androidstudio.mjs │ │ ├── atomone.mjs │ │ ├── aura.mjs │ │ ├── bbedit.mjs │ │ ├── blackscreen.mjs │ │ ├── bluescreen.mjs │ │ ├── darcula.mjs │ │ ├── dracula.mjs │ │ ├── duotoneDark.mjs │ │ ├── duotoneLight.mjs │ │ ├── eclipse.mjs │ │ ├── githubDark.mjs │ │ ├── githubLight.mjs │ │ ├── green-text.mjs │ │ ├── gruvboxDark.mjs │ │ ├── gruvboxLight.mjs │ │ ├── materialDark.mjs │ │ ├── materialLight.mjs │ │ ├── monokai.mjs │ │ ├── noctisLilac.mjs │ │ ├── nord.mjs │ │ ├── red-text.mjs │ │ ├── solarizedDark.mjs │ │ ├── solarizedLight.mjs │ │ ├── sonic-pink.mjs │ │ ├── strudel-theme.mjs │ │ ├── sublime.mjs │ │ ├── teletext.mjs │ │ ├── terminal.mjs │ │ ├── theme-helper.mjs │ │ ├── tokioNightStorm.mjs │ │ ├── tokyoNight.mjs │ │ ├── tokyoNightDay.mjs │ │ ├── vscodeDark.mjs │ │ ├── vscodeLight.mjs │ │ ├── whitescreen.mjs │ │ └── xcodeLight.mjs │ ├── tooltip.mjs │ ├── vite.config.js │ └── widget.mjs ├── core │ ├── .npmignore │ ├── README.md │ ├── bench │ │ └── pattern.bench.mjs │ ├── clockworker.js │ ├── controls.mjs │ ├── cyclist.mjs │ ├── drawLine.mjs │ ├── euclid.mjs │ ├── evaluate.mjs │ ├── fraction.mjs │ ├── hap.mjs │ ├── index.mjs │ ├── logger.mjs │ ├── neocyclist.mjs │ ├── package.json │ ├── pattern.mjs │ ├── pick.mjs │ ├── repl.mjs │ ├── signal.mjs │ ├── speak.mjs │ ├── state.mjs │ ├── test │ │ ├── controls.test.mjs │ │ ├── drawLine.test.mjs │ │ ├── fraction.test.mjs │ │ ├── pattern.test.mjs │ │ ├── solmization.test.js │ │ ├── util.test.mjs │ │ └── value.test.mjs │ ├── time.mjs │ ├── timespan.mjs │ ├── ui.mjs │ ├── util.mjs │ ├── value.mjs │ ├── vite.config.js │ └── zyklus.mjs ├── csound │ ├── README.md │ ├── index.mjs │ ├── livecode.orc │ ├── package.json │ ├── presets.orc │ ├── project.csd │ └── vite.config.js ├── desktopbridge │ ├── README.md │ ├── index.mjs │ ├── loggerbridge.mjs │ ├── midibridge.mjs │ ├── oscbridge.mjs │ ├── package.json │ └── utils.mjs ├── draw │ ├── README.md │ ├── animate.mjs │ ├── color.mjs │ ├── draw.mjs │ ├── index.mjs │ ├── package.json │ ├── pianoroll.mjs │ ├── pitchwheel.mjs │ ├── spiral.mjs │ └── vite.config.js ├── embed │ ├── README.md │ ├── embed.js │ └── package.json ├── gamepad │ ├── README.md │ ├── docs │ │ └── gamepad.mdx │ ├── gamepad.mjs │ ├── index.mjs │ ├── package.json │ └── vite.config.js ├── hs2js │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── hs2js.mjs │ │ ├── index.mjs │ │ └── parser.mjs │ └── vite.config.js ├── hydra │ ├── README.md │ ├── hydra.mjs │ ├── package.json │ └── vite.config.js ├── midi │ ├── README.md │ ├── index.mjs │ ├── midi.mjs │ ├── package.json │ └── vite.config.js ├── mini │ ├── README.md │ ├── bench │ │ └── mini.bench.mjs │ ├── index.mjs │ ├── krill-parser.js │ ├── krill.pegjs │ ├── mini.mjs │ ├── package.json │ ├── test │ │ └── mini.test.mjs │ └── vite.config.js ├── motion │ ├── README.md │ ├── docs │ │ └── devicemotion.mdx │ ├── index.mjs │ ├── motion.mjs │ ├── package.json │ └── vite.config.js ├── mqtt │ ├── README.md │ ├── mqtt.mjs │ ├── package.json │ └── vite.config.js ├── osc │ ├── README.md │ ├── index.html │ ├── osc.mjs │ ├── package.json │ ├── server.js │ ├── superdirtoutput.js │ ├── tidal-sniffer.js │ └── vite.config.js ├── reference │ ├── README.md │ ├── index.mjs │ ├── package.json │ └── vite.config.js ├── repl │ ├── .gitignore │ ├── README.md │ ├── index.mjs │ ├── package.json │ ├── prebake.mjs │ ├── repl-component.mjs │ └── vite.config.js ├── sampler │ ├── README.md │ ├── package.json │ └── sample-server.mjs ├── serial │ ├── README.md │ ├── package.json │ ├── serial.mjs │ └── vite.config.js ├── soundfonts │ ├── README.md │ ├── convert.js │ ├── fontloader.mjs │ ├── gm.mjs │ ├── index.mjs │ ├── list.mjs │ ├── package.json │ ├── sfumato.mjs │ └── vite.config.js ├── superdough │ ├── README.md │ ├── dspworklet.mjs │ ├── feedbackdelay.mjs │ ├── fft.js │ ├── helpers.mjs │ ├── index.mjs │ ├── logger.mjs │ ├── noise.mjs │ ├── ola-processor.js │ ├── package.json │ ├── reverb.mjs │ ├── reverbGen.mjs │ ├── sampler.mjs │ ├── superdough.mjs │ ├── synth.mjs │ ├── util.mjs │ ├── vite.config.js │ ├── vowel.mjs │ ├── worklets.mjs │ ├── zzfx.mjs │ └── zzfx_fork.mjs ├── tidal │ ├── package.json │ └── tidal.mjs ├── tonal │ ├── README.md │ ├── index.mjs │ ├── ireal.mjs │ ├── package.json │ ├── test │ │ ├── tonal.test.mjs │ │ └── tonleiter.test.mjs │ ├── tonal.mjs │ ├── tonleiter.mjs │ ├── vite.config.js │ └── voicings.mjs ├── transpiler │ ├── README.md │ ├── index.mjs │ ├── package.json │ ├── test │ │ └── transpiler.test.mjs │ ├── transpiler.mjs │ └── vite.config.js ├── vite-plugin-bundle-audioworklet │ ├── package.json │ └── vite-plugin-bundle-audioworklet.js ├── web │ ├── README.md │ ├── package.json │ ├── vite.config.js │ └── web.mjs ├── webaudio │ ├── README.md │ ├── index.mjs │ ├── package.json │ ├── scope.mjs │ ├── spectrum.mjs │ ├── vite.config.js │ └── webaudio.mjs └── xen │ ├── README.md │ ├── index.mjs │ ├── package.json │ ├── test │ └── xen.test.mjs │ ├── tune.mjs │ ├── tunejs.js │ ├── vite.config.js │ └── xen.mjs ├── paper ├── Makefile ├── README.md ├── bin │ └── code-filter.py ├── citations.json ├── demo-preprocessed.md ├── demo.md ├── demo.pdf ├── iclc-reviews.md ├── iclc2023.html ├── iclc2023.md ├── iclc2023.pdf ├── iclc2023x.pdf ├── images │ ├── cc.png │ ├── strudel-screenshot.png │ ├── strudel-screenshot2.png │ └── strudelflow.png ├── inconsolata.sty ├── make.sh ├── pandoc │ ├── iclc.html │ ├── iclc.latex │ └── iclc.sty ├── paper-preprocessed.md ├── paper.md ├── paper.pdf └── tex │ ├── latex-template.tex │ ├── sig-alternate.cls │ └── waccopyright.sty ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── samples └── README.md ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── src │ ├── loggerbridge.rs │ ├── main.rs │ ├── midibridge.rs │ └── oscbridge.rs └── tauri.conf.json ├── test ├── __snapshots__ │ ├── examples.test.mjs.snap │ └── tunes.test.mjs.snap ├── examples.test.mjs ├── metadata.test.mjs ├── runtime.mjs ├── testtunes.mjs └── tunes.test.mjs ├── tools └── dbpatch │ ├── README.md │ ├── dbpatch.mjs │ └── package.json ├── undocumented.json ├── vitest.config.mjs └── website ├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── README.md ├── agpl-header.txt ├── astro.config.mjs ├── database.types.ts ├── package.json ├── public ├── CNAME ├── EmuSP12.json ├── favicon.ico ├── fonts │ ├── 3270 │ │ ├── 3270-Regular.ttf │ │ └── LICENSE.txt │ ├── BigBlueTerminal │ │ ├── BigBlue_TerminalPlus.TTF │ │ └── LICENSE.txt │ ├── CutiePi │ │ ├── Cute_Aurora_demo.ttf │ │ └── LICENSE.txt │ ├── FiraCode │ │ ├── FiraCode-Regular.ttf │ │ ├── FiraCode-SemiBold.ttf │ │ └── LICENSE.txt │ ├── Hack │ │ ├── Hack-Regular.ttf │ │ └── license.txt │ ├── JetBrains │ │ ├── JetBrainsMono.woff2 │ │ └── license.txt │ ├── Monocraft │ │ ├── Monocraft.ttf │ │ └── license.txt │ ├── PressStart2P │ │ ├── OFL.txt │ │ └── PressStart2P-Regular.ttf │ ├── galactico │ │ ├── Galactico-Basic.otf │ │ └── fontinfo.txt │ ├── mode7 │ │ ├── MODE7GX3.TTF │ │ └── credit.txt │ ├── teletext │ │ ├── EuropeanTeletext.ttf │ │ ├── EuropeanTeletextNuevo.ttf │ │ └── LICENSE.txt │ └── we-come-in-peace │ │ ├── fontinfo.txt │ │ └── we-come-in-peace-bb.regular.ttf ├── icon.png ├── icons │ ├── apple-icon-180.png │ ├── manifest-icon-192.maskable.png │ ├── manifest-icon-512.maskable.png │ └── strudel_icon.png ├── img │ ├── autocomplete.png │ ├── drumset.png │ ├── strudel-alien-live-coding.png │ ├── strudel-collaborative-coding.png │ ├── strudel-live-coding-mars-college.jpg │ ├── strudel-monkeys.png │ ├── strudel-scope.png │ ├── strudel-themes.png │ └── workshop-space.png ├── logo.svg ├── make-scrollable-code-focusable.js ├── manifest.json ├── mridangam.json ├── piano.json ├── pwa │ ├── strudel-linux.png │ └── strudel-macos.png ├── robots.txt ├── tidal-drum-machines-alias.json ├── tidal-drum-machines.json └── vcsl.json ├── src ├── components │ ├── BlogPost.astro │ ├── BlogVideo.astro │ ├── Box.astro │ ├── Claviature.jsx │ ├── Footer │ │ └── AvatarList.astro │ ├── HeadCommon.astro │ ├── HeadCommonNext.astro │ ├── HeadSEO.astro │ ├── Header │ │ ├── Header.astro │ │ ├── LanguageSelect.css │ │ ├── LanguageSelect.tsx │ │ ├── Search.css │ │ └── Search.tsx │ ├── LeftSidebar │ │ └── LeftSidebar.astro │ ├── Oven │ │ └── Oven.jsx │ ├── PageContent │ │ └── PageContent.astro │ ├── PitchSlider.jsx │ ├── QA.tsx │ ├── RightSidebar │ │ ├── MoreMenu.astro │ │ ├── RightSidebar.astro │ │ └── TableOfContents.tsx │ ├── Showcase.jsx │ ├── Udels │ │ ├── UdelFrame.jsx │ │ ├── Udels.jsx │ │ ├── UdelsEditor.jsx │ │ └── UdelsHeader.jsx │ └── Youtube.jsx ├── config.ts ├── content │ ├── blog │ │ ├── release-0.0.2-schwindlig.mdx │ │ ├── release-0.0.2.1-stuermisch.mdx │ │ ├── release-0.0.3-maelstrom.mdx │ │ ├── release-0.0.4-gischt.mdx │ │ ├── release-0.3.0-donauwelle.mdx │ │ ├── release-0.4.0-brandung.mdx │ │ ├── release-0.5.0-wirbel.mdx │ │ ├── release-0.6.0-zimtschnecke.mdx │ │ ├── release-0.7.0-zuckerguss.mdx │ │ ├── release-0.8.0-himbeermuffin.mdx │ │ ├── release-0.9.0-bananenbrot.mdx │ │ ├── release-1.0.0-geburtstagskuchen.mdx │ │ └── year-2.mdx │ └── config.ts ├── cx.mjs ├── docs │ ├── ApiDoc.jsx │ ├── Colors.jsx │ ├── Icon.jsx │ ├── JsDoc.astro │ ├── JsDoc.jsx │ ├── MiniRepl.astro │ ├── MiniRepl.css │ ├── MiniRepl.jsx │ ├── MobileNav.jsx │ ├── docs.css │ └── link.svg ├── env.d.ts ├── examples.mjs ├── languages.ts ├── layouts │ └── MainLayout.astro ├── metadata_parser.js ├── my_patterns.js ├── pages │ ├── bakery.astro │ ├── blog.astro │ ├── de │ │ └── workshop │ │ │ ├── first-effects.mdx │ │ │ ├── first-notes.mdx │ │ │ ├── first-sounds.mdx │ │ │ ├── getting-started.mdx │ │ │ ├── index.astro │ │ │ ├── pattern-effects.mdx │ │ │ └── recap.mdx │ ├── embed.astro │ ├── examples │ │ └── index.astro │ ├── functions │ │ ├── intro.mdx │ │ └── value-modifiers.mdx │ ├── index.astro │ ├── intro │ │ └── showcase.mdx │ ├── learn │ │ ├── accumulation.mdx │ │ ├── code.mdx │ │ ├── colors.mdx │ │ ├── conditional-modifiers.mdx │ │ ├── csound.mdx │ │ ├── devicemotion.mdx │ │ ├── effects.mdx │ │ ├── factories.mdx │ │ ├── getting-started.mdx │ │ ├── hydra.mdx │ │ ├── index.astro │ │ ├── input-devices.mdx │ │ ├── input-output.mdx │ │ ├── metadata.mdx │ │ ├── mini-notation.mdx │ │ ├── notes.mdx │ │ ├── pwa.mdx │ │ ├── random-modifiers.mdx │ │ ├── samples.mdx │ │ ├── signals.mdx │ │ ├── sounds.mdx │ │ ├── stepwise.mdx │ │ ├── strudel-vs-tidal.mdx │ │ ├── synths.mdx │ │ ├── time-modifiers.mdx │ │ ├── tonal.mdx │ │ └── visual-feedback.mdx │ ├── recipes │ │ ├── arpeggios.mdx │ │ ├── microrhythms.mdx │ │ ├── recipes.mdx │ │ └── rhythms.mdx │ ├── rss.xml.js │ ├── swatch │ │ └── index.astro │ ├── technical-manual │ │ ├── about.mdx │ │ ├── alignment.mdx │ │ ├── docs.mdx │ │ ├── internals.mdx │ │ ├── packages.mdx │ │ ├── patterns.mdx │ │ ├── project-start.mdx │ │ ├── repl.mdx │ │ ├── sounds.mdx │ │ └── testing.mdx │ ├── tutorial │ │ └── index.astro │ ├── udels │ │ └── index.astro │ ├── understand │ │ ├── cycles.mdx │ │ ├── pitch.mdx │ │ └── voicings.mdx │ └── workshop │ │ ├── first-effects.mdx │ │ ├── first-notes.mdx │ │ ├── first-sounds.mdx │ │ ├── getting-started.mdx │ │ ├── index.astro │ │ ├── pattern-effects.mdx │ │ └── recap.mdx ├── pwa.ts ├── repl │ ├── Repl.css │ ├── Repl.jsx │ ├── components │ │ ├── BigPlayButton.jsx │ │ ├── Code.jsx │ │ ├── EmbeddedReplEditor.jsx │ │ ├── Header.jsx │ │ ├── Loader.jsx │ │ ├── NumberInput.jsx │ │ ├── ReplEditor.jsx │ │ ├── UserFacingErrorMessage.jsx │ │ ├── incrementor │ │ │ └── Incrementor.jsx │ │ ├── pagination │ │ │ └── Pagination.jsx │ │ ├── panel │ │ │ ├── AudioDeviceSelector.jsx │ │ │ ├── AudioEngineTargetSelector.jsx │ │ │ ├── ConsoleTab.jsx │ │ │ ├── FilesTab.jsx │ │ │ ├── Forms.jsx │ │ │ ├── ImportSoundsButton.jsx │ │ │ ├── Panel.jsx │ │ │ ├── PatternsTab.jsx │ │ │ ├── Reference.jsx │ │ │ ├── SelectInput.jsx │ │ │ ├── SettingsTab.jsx │ │ │ ├── SoundsTab.jsx │ │ │ └── WelcomeTab.jsx │ │ ├── textbox │ │ │ └── Textbox.jsx │ │ ├── useLogger.jsx │ │ └── usedebounce.jsx │ ├── drawings.mjs │ ├── drum_patterns.mjs │ ├── favicon.ico │ ├── files.mjs │ ├── idbutils.mjs │ ├── piano.mjs │ ├── prebake.mjs │ ├── tunes.mjs │ ├── useExamplePatterns.jsx │ ├── useReplContext.jsx │ └── util.mjs ├── settings.mjs ├── styles │ └── index.css ├── tauri.mjs ├── useClient.mjs ├── useEvent.mjs ├── useFrame.mjs └── user_pattern_utils.mjs ├── tailwind.config.cjs └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true, 5 | "es2021": true 6 | }, 7 | "extends": ["eslint:recommended"], 8 | "overrides": [], 9 | "parserOptions": { 10 | "ecmaVersion": "latest", 11 | "sourceType": "module" 12 | }, 13 | "plugins": ["import"], 14 | "rules": { 15 | "no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }], 16 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}] 17 | }, 18 | "files": ["**/*.mjs", "**/*.js"] 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: tidalcycles 4 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: [workflow_dispatch] 4 | 5 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 6 | permissions: 7 | contents: read 8 | pages: write 9 | id-token: write 10 | deployments: write 11 | 12 | # Allow one concurrent deployment 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | environment: 21 | name: github-pages 22 | url: ${{ steps.deployment.outputs.page_url }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: pnpm/action-setup@v4 26 | with: 27 | version: 9.12.2 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 20 31 | cache: "pnpm" 32 | - name: Install Dependencies 33 | run: pnpm install 34 | 35 | - name: Build 36 | run: pnpm build 37 | 38 | - name: Setup Pages 39 | uses: actions/configure-pages@v2 40 | 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v3 43 | with: 44 | # Upload entire repository 45 | path: "./website/dist" 46 | 47 | - name: Deploy to GitHub Pages 48 | id: deployment 49 | uses: actions/deploy-pages@v4 50 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Strudel tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [20] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: pnpm/action-setup@v4 15 | with: 16 | version: 9.12.2 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | cache: 'pnpm' 21 | - run: pnpm install 22 | - run: pnpm run format-check 23 | - run: pnpm run lint 24 | - run: pnpm test 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | *.json 3 | *.yml 4 | *. 5 | **/out 6 | **/dist 7 | packages/mini/krill-parser.js 8 | packages/xen/tunejs.js 9 | paper 10 | pnpm-lock.yaml 11 | pnpm-workspace.yaml 12 | **/dev-dist 13 | website/.astro 14 | !tidal-drum-machines.json 15 | !tidal-drum-machines-alias.json 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "useTabs": false, 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": true, 7 | "jsxSingleQuote": false, 8 | "trailingComma": "all", 9 | "bracketSpacing": true, 10 | "bracketSameLine": false, 11 | "arrowParens": "always", 12 | "proseWrap": "preserve", 13 | "htmlWhitespaceSensitivity": "css", 14 | "endOfLine": "lf" 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "subspan", 4 | "vals" 5 | ], 6 | "yaml.schemas": { 7 | "https://json.schemastore.org/github-workflow.json": "file:///home/felix/projects/strudel/.github/workflows/deploy.yml" 8 | }, 9 | "testing.automaticallyOpenPeekView": "never" 10 | } -------------------------------------------------------------------------------- /bench/tunes.bench.mjs: -------------------------------------------------------------------------------- 1 | import { queryCode, testCycles } from '../test/runtime.mjs'; 2 | import * as tunes from '../website/src/repl/tunes.mjs'; 3 | import { describe, bench } from 'vitest'; 4 | import { calculateSteps } from '../packages/core/index.mjs'; 5 | 6 | const tuneKeys = Object.keys(tunes); 7 | 8 | describe('renders tunes', () => { 9 | tuneKeys.forEach((key) => { 10 | describe(key, () => { 11 | calculateSteps(true); 12 | bench(`+steps`, async () => { 13 | await queryCode(tunes[key], testCycles[key] || 1); 14 | }); 15 | calculateSteps(false); 16 | bench(`-steps`, async () => { 17 | await queryCode(tunes[key], testCycles[key] || 1); 18 | }); 19 | calculateSteps(true); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # examples 2 | 3 | This folder contains usage examples for different scenarios. 4 | -------------------------------------------------------------------------------- /examples/buildless/README.md: -------------------------------------------------------------------------------- 1 | # buildless examples 2 | 3 | These examples show you how strudel can be used in a regular html file, without the need for a build tool. 4 | 5 | Most examples are using [skypack](https://www.skypack.dev/) 6 | -------------------------------------------------------------------------------- /examples/buildless/basic.html: -------------------------------------------------------------------------------- 1 | 8 |
9 | 23 |

24 | This page shows how skypack can be used to import strudel core directly into a simple html page.
25 | No server, no bundler and no build setup is needed to run this! 26 |

27 | -------------------------------------------------------------------------------- /examples/buildless/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 34 | 35 | -------------------------------------------------------------------------------- /examples/buildless/headless-simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/buildless/headless-with-samples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/buildless/hs2js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /examples/buildless/web-component-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /examples/buildless/web-component-no-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31 | 32 | -------------------------------------------------------------------------------- /examples/buildless/web.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /examples/codemirror-repl/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/codemirror-repl/README.md: -------------------------------------------------------------------------------- 1 | # codemirror-repl example 2 | 3 | This folder demonstrates how to set up a full strudel repl with the `@strudel/codemirror` package. Run it using: 4 | 5 | ```sh 6 | pnpm i 7 | pnpm dev 8 | ``` 9 | -------------------------------------------------------------------------------- /examples/codemirror-repl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite Vanilla Strudel REPL 7 | 8 | 9 |
10 | 14 |
15 |
16 |
17 |
18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/codemirror-repl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-vanilla-repl-cm6", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^6.0.11" 13 | }, 14 | "dependencies": { 15 | "@strudel/codemirror": "workspace:*", 16 | "@strudel/core": "workspace:*", 17 | "@strudel/draw": "workspace:*", 18 | "@strudel/mini": "workspace:*", 19 | "@strudel/soundfonts": "workspace:*", 20 | "@strudel/tonal": "workspace:*", 21 | "@strudel/transpiler": "workspace:*", 22 | "@strudel/webaudio": "workspace:*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/codemirror-repl/style.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | margin: 0; 4 | height: 100%; 5 | background: #282c34; 6 | } 7 | 8 | main { 9 | height: 100%; 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | 14 | .container { 15 | flex-grow: 1; 16 | max-height: 100%; 17 | position: relative; 18 | overflow: auto; 19 | } 20 | 21 | #editor { 22 | overflow: auto; 23 | } 24 | 25 | .cm-editor { 26 | height: 100%; 27 | } 28 | 29 | #roll { 30 | height: 300px; 31 | } 32 | -------------------------------------------------------------------------------- /examples/headless-repl/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/headless-repl/README.md: -------------------------------------------------------------------------------- 1 | # headless-repl demo 2 | 3 | This demo shows how to use strudel in "headless mode". 4 | Buttons A / B / C will switch between different patterns. 5 | It showcases the usage of the `@strudel/web` package, using [vite](https://vitejs.dev/) as the dev server. 6 | 7 | ## Running 8 | 9 | ```sh 10 | pnpm i && cd examples/headless-repl 11 | pnpm dev 12 | # open http://localhost:5173/ 13 | ``` 14 | -------------------------------------------------------------------------------- /examples/headless-repl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @strudel/web REPL Example 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/headless-repl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repl-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "license": "AGPL-3.0-or-later", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vite build", 10 | "preview": "vite preview" 11 | }, 12 | "devDependencies": { 13 | "vite": "^6.0.11" 14 | }, 15 | "dependencies": { 16 | "@strudel/web": "workspace:*" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/minimal-repl/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /examples/minimal-repl/README.md: -------------------------------------------------------------------------------- 1 | # minimal repl 2 | 3 | This folder demonstrates how to set up a minimal strudel repl using vite and vanilla JS. Run it using: 4 | 5 | ```sh 6 | npm i 7 | npm run dev 8 | ``` 9 | 10 | If you're looking for a more feature rich alternative, have a look at the [../codemirror-repl](codemirror-repl example) 11 | -------------------------------------------------------------------------------- /examples/minimal-repl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite Vanilla Strudel REPL 7 | 8 | 9 |
10 | 15 |
16 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/minimal-repl/main.js: -------------------------------------------------------------------------------- 1 | import { repl, evalScope } from '@strudel/core'; 2 | import { getAudioContext, webaudioOutput, initAudioOnFirstClick, registerSynthSounds } from '@strudel/webaudio'; 3 | import { transpiler } from '@strudel/transpiler'; 4 | import tune from './tune.mjs'; 5 | 6 | const ctx = getAudioContext(); 7 | const input = document.getElementById('text'); 8 | input.innerHTML = tune; 9 | initAudioOnFirstClick(); 10 | registerSynthSounds(); 11 | 12 | evalScope(import('@strudel/core'), import('@strudel/mini'), import('@strudel/webaudio'), import('@strudel/tonal')); 13 | 14 | const { evaluate } = repl({ 15 | defaultOutput: webaudioOutput, 16 | getTime: () => ctx.currentTime, 17 | transpiler, 18 | }); 19 | document.getElementById('start').addEventListener('click', () => { 20 | ctx.resume(); 21 | console.log('eval', input.value); 22 | evaluate(input.value); 23 | }); 24 | -------------------------------------------------------------------------------- /examples/minimal-repl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-vanilla-repl", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build-githack": "vite build --base /tidalcycles/strudel/use-acorn/packages/core/examples/vite-vanilla-repl/dist/", 9 | "build": "vite build", 10 | "preview": "vite preview" 11 | }, 12 | "devDependencies": { 13 | "vite": "^6.0.11" 14 | }, 15 | "dependencies": { 16 | "@strudel/core": "workspace:*", 17 | "@strudel/mini": "workspace:*", 18 | "@strudel/transpiler": "workspace:*", 19 | "@strudel/webaudio": "workspace:*", 20 | "@strudel/tonal": "workspace:*" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/minimal-repl/tune.mjs: -------------------------------------------------------------------------------- 1 | export default `samples('github:tidalcycles/dirt-samples') 2 | setcps(1) 3 | stack( 4 | // amen 5 | n("0 1 2 3 4 5 6 7") 6 | .sometimes(x=>x.ply(2)) 7 | .rarely(x=>x.speed("2 | -2")) 8 | .sometimesBy(.4, x=>x.delay(".5")) 9 | .s("amencutup") 10 | .slow(2) 11 | .room(.5) 12 | , 13 | // bass 14 | sine.add(saw.slow(4)).range(0,7).segment(8) 15 | .superimpose(x=>x.add(.1)) 16 | .scale('G0 minor').note() 17 | .s("sawtooth").decay(.1).sustain(0) 18 | .gain(.4).cutoff(perlin.range(300,3000).slow(8)).resonance(10) 19 | .degradeBy("0 0.1 .5 .1") 20 | .rarely(add(note("12"))) 21 | , 22 | // chord 23 | note("Bb3,D4".superimpose(x=>x.add(.2))) 24 | .s('sawtooth').cutoff(1000).struct("<~@3 [~ x]>") 25 | .decay(.05).sustain(.0).delay(.8).delaytime(.125).room(.8) 26 | , 27 | // alien 28 | s("breath").room(1).shape(.6).chop(16).rev().mask("") 29 | , 30 | n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5)) 31 | ).reset("")`; 32 | -------------------------------------------------------------------------------- /examples/superdough/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/superdough/README.md: -------------------------------------------------------------------------------- 1 | # superdough demo 2 | 3 | This demo shows how to use [superdough](https://www.npmjs.com/package/superdough) with [vite](https://vitejs.dev/). 4 | 5 | ## Running 6 | 7 | ```sh 8 | pnpm i && cd examples/headless-repl 9 | pnpm dev 10 | # open http://localhost:5173/ 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/superdough/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Superdough Example 5 | 6 | 7 | 8 | 9 | 10 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/superdough/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "superdough-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "superdough": "workspace:*" 13 | }, 14 | "devDependencies": { 15 | "vite": "^6.0.11" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/tidal-repl/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | public/tree-sitter.wasm 27 | public/tree-sitter-haskell.wasm 28 | -------------------------------------------------------------------------------- /examples/tidal-repl/README.md: -------------------------------------------------------------------------------- 1 | # @strudel/tidal 2 | 3 | This is an experiment in implementing tree-sitter for parsing haskell. 4 | 5 | ```sh 6 | pnpm i 7 | cd haskell 8 | pnpm copy-wasm 9 | pnpm dev 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/tidal-repl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tree sitter test 7 | 22 | 23 | 24 | 25 |

26 |     
31 |   
32 | 
33 | 


--------------------------------------------------------------------------------
/examples/tidal-repl/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "tidal-repl",
 3 |   "private": true,
 4 |   "version": "0.0.1",
 5 |   "type": "module",
 6 |   "scripts": {
 7 |     "dev": "vite",
 8 |     "build": "pnpm copy-wasm && vite build",
 9 |     "preview": "vite preview",
10 |     "copy-wasm": "cp node_modules/hs2js/dist/tree-sitter.wasm public && cp node_modules/hs2js/dist/tree-sitter-haskell.wasm public"
11 |   },
12 |   "repository": {
13 |     "type": "git",
14 |     "url": "git+https://github.com/tidalcycles/strudel.git"
15 |   },
16 |   "keywords": [
17 |     "titdalcycles",
18 |     "strudel",
19 |     "pattern",
20 |     "livecoding",
21 |     "algorave"
22 |   ],
23 |   "author": "Felix Roos ",
24 |   "license": "AGPL-3.0-or-later",
25 |   "bugs": {
26 |     "url": "https://github.com/tidalcycles/strudel/issues"
27 |   },
28 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
29 |   "dependencies": {
30 |     "@strudel/web": "workspace:*",
31 |     "hs2js": "workspace:*"
32 |   },
33 |   "devDependencies": {
34 |     "vite": "^6.0.11"
35 |   }
36 | }
37 | 


--------------------------------------------------------------------------------
/examples/tidal-repl/public/README.md:
--------------------------------------------------------------------------------
1 | # public
2 | 
3 | this file is just here to make sure the public folder exists
4 | 


--------------------------------------------------------------------------------
/index.mjs:
--------------------------------------------------------------------------------
 1 | // this barrel export is currently only used to find undocumented exports
 2 | export * from './packages/codemirror/index.mjs';
 3 | export * from './packages/core/index.mjs';
 4 | export * from './packages/csound/index.mjs';
 5 | export * from './packages/desktopbridge/index.mjs';
 6 | export * from './packages/draw/index.mjs';
 7 | export * from './packages/embed/index.mjs';
 8 | export * from './packages/hydra/index.mjs';
 9 | export * from './packages/midi/index.mjs';
10 | export * from './packages/mini/index.mjs';
11 | export * from './packages/osc/index.mjs';
12 | export * from './packages/repl/index.mjs';
13 | export * from './packages/serial/index.mjs';
14 | export * from './packages/soundfonts/index.mjs';
15 | export * from './packages/superdough/index.mjs';
16 | export * from './packages/tonal/index.mjs';
17 | export * from './packages/transpiler/index.mjs';
18 | export * from './packages/web/index.mjs';
19 | export * from './packages/webaudio/index.mjs';
20 | export * from './packages/xen/index.mjs';
21 | 


--------------------------------------------------------------------------------
/jsdoc/jsdoc-synonyms.js:
--------------------------------------------------------------------------------
 1 | /*
 2 | jsdoc-synonyms.js - Add support for @synonym tag
 3 | Copyright (C) 2023 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | function defineTags(dictionary) {
 8 |   dictionary.defineTag('synonyms', {
 9 |     mustHaveValue: true,
10 |     onTagged: function (doclet, tag) {
11 |       doclet.synonyms_text = tag.value;
12 |       doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/);
13 |     },
14 |   });
15 | }
16 | 
17 | module.exports = { defineTags: defineTags };
18 | 


--------------------------------------------------------------------------------
/jsdoc/jsdoc.config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "source": {
 3 |     "includePattern": ".+\\.(js(doc|x)?|mjs)$",
 4 |     "excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
 5 |   },
 6 |   "plugins": ["plugins/markdown", "jsdoc/jsdoc-synonyms"],
 7 |   "opts": {
 8 |     "destination": "./out/",
 9 |     "recurse": true
10 |   }
11 | }
12 | 


--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 |   "packages": ["packages/*"],
3 |   "version": "independent",
4 |   "npmClient": "pnpm",
5 |   "$schema": "node_modules/lerna/schemas/lerna-schema.json"
6 | }
7 | 


--------------------------------------------------------------------------------
/packages/README.md:
--------------------------------------------------------------------------------
1 | # Packages
2 | 
3 | Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel).
4 | 
5 | To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
6 | 


--------------------------------------------------------------------------------
/packages/codemirror/README.md:
--------------------------------------------------------------------------------
1 | # @strudel/codemirror
2 | 
3 | This package contains helpers and extensions to use codemirror6. See [vite-vanilla-repl-cm6](../core/examples/vite-vanilla-repl-cm6/main.js) as an example of using it.
4 | 


--------------------------------------------------------------------------------
/packages/codemirror/flash.mjs:
--------------------------------------------------------------------------------
 1 | import { StateEffect, StateField } from '@codemirror/state';
 2 | import { Decoration, EditorView } from '@codemirror/view';
 3 | 
 4 | export const setFlash = StateEffect.define();
 5 | export const flashField = StateField.define({
 6 |   create() {
 7 |     return Decoration.none;
 8 |   },
 9 |   update(flash, tr) {
10 |     try {
11 |       for (let e of tr.effects) {
12 |         if (e.is(setFlash)) {
13 |           if (e.value && tr.newDoc.length > 0) {
14 |             const mark = Decoration.mark({
15 |               attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` },
16 |             });
17 |             flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
18 |           } else {
19 |             flash = Decoration.set([]);
20 |           }
21 |         }
22 |       }
23 |       return flash;
24 |     } catch (err) {
25 |       console.warn('flash error', err);
26 |       return flash;
27 |     }
28 |   },
29 |   provide: (f) => EditorView.decorations.from(f),
30 | });
31 | 
32 | export const flash = (view, ms = 200) => {
33 |   view.dispatch({ effects: setFlash.of(true) });
34 |   setTimeout(() => {
35 |     view.dispatch({ effects: setFlash.of(false) });
36 |   }, ms);
37 | };
38 | 
39 | export const isFlashEnabled = (on) => (on ? flashField : []);
40 | 


--------------------------------------------------------------------------------
/packages/codemirror/html.mjs:
--------------------------------------------------------------------------------
 1 | const parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null;
 2 | export let html = (string) => {
 3 |   return parser?.parseFromString(string, 'text/html').querySelectorAll('*');
 4 | };
 5 | let parseChunk = (chunk) => {
 6 |   if (Array.isArray(chunk)) return chunk.flat().join('');
 7 |   if (chunk === undefined) return '';
 8 |   return chunk;
 9 | };
10 | export let h = (strings, ...vars) => {
11 |   let string = '';
12 |   for (let i in strings) {
13 |     string += parseChunk(strings[i]);
14 |     string += parseChunk(vars[i]);
15 |   }
16 |   return html(string);
17 | };
18 | 


--------------------------------------------------------------------------------
/packages/codemirror/index.mjs:
--------------------------------------------------------------------------------
1 | export * from './codemirror.mjs';
2 | export * from './highlight.mjs';
3 | export * from './flash.mjs';
4 | export * from './slider.mjs';
5 | export * from './themes.mjs';
6 | export * from './widget.mjs';
7 | 


--------------------------------------------------------------------------------
/packages/codemirror/keybindings.mjs:
--------------------------------------------------------------------------------
 1 | import { Prec } from '@codemirror/state';
 2 | import { keymap, ViewPlugin } from '@codemirror/view';
 3 | // import { searchKeymap } from '@codemirror/search';
 4 | import { emacs } from '@replit/codemirror-emacs';
 5 | import { vim } from '@replit/codemirror-vim';
 6 | import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
 7 | import { defaultKeymap, historyKeymap } from '@codemirror/commands';
 8 | 
 9 | const vscodePlugin = ViewPlugin.fromClass(
10 |   class {
11 |     constructor() {}
12 |   },
13 |   {
14 |     provide: () => {
15 |       return Prec.highest(keymap.of([...vscodeKeymap]));
16 |     },
17 |   },
18 | );
19 | const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
20 | 
21 | const keymaps = {
22 |   vim,
23 |   emacs,
24 |   vscode: vscodeExtension,
25 | };
26 | 
27 | export function keybindings(name) {
28 |   const active = keymaps[name];
29 |   return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
30 |   // keymap.of(searchKeymap),
31 | }
32 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/CutiePi.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Cutie Pi
 3 |  * by Switch Angel
 4 |  */
 5 | import { tags as t } from '@lezer/highlight';
 6 | import { createTheme } from './theme-helper.mjs';
 7 | const deepPurple = '#5c019a';
 8 | const yellowPink = '#fbeffc';
 9 | const grey = '#272C35';
10 | const pinkAccent = '#fee1ff';
11 | const lightGrey = '#465063';
12 | const bratGreen = '#9acd3f';
13 | const lighterGrey = '#97a1b7';
14 | const pink = '#f6a6fd';
15 | 
16 | export const settings = {
17 |   background: 'white',
18 |   lineBackground: 'transparent',
19 |   foreground: deepPurple,
20 |   caret: '#797977',
21 |   selection: yellowPink,
22 |   selectionMatch: '#2B323D',
23 |   gutterBackground: grey,
24 |   gutterForeground: lightGrey,
25 |   gutterBorder: 'transparent',
26 |   lineHighlight: pinkAccent,
27 | };
28 | 
29 | export default createTheme({
30 |   theme: 'light',
31 |   settings,
32 |   styles: [
33 |     {
34 |       tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
35 |       color: deepPurple,
36 |     },
37 |     { tag: [t.tagName, t.heading], color: settings.foreground },
38 |     { tag: t.comment, color: lighterGrey },
39 |     { tag: [t.variableName, t.propertyName, t.labelName], color: pink },
40 |     { tag: [t.attributeName, t.number], color: '#d19a66' },
41 |     { tag: t.className, color: grey },
42 |     { tag: t.keyword, color: deepPurple },
43 |     { tag: [t.string, t.regexp, t.special(t.propertyName)], color: bratGreen },
44 |   ],
45 | });
46 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/androidstudio.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * androidstudio
 3 |  */
 4 | import { tags as t } from '@lezer/highlight';
 5 | import { createTheme } from './theme-helper.mjs';
 6 | 
 7 | export const settings = {
 8 |   background: '#282b2e',
 9 |   lineBackground: '#282b2e99',
10 |   foreground: '#a9b7c6',
11 |   caret: '#00FF00',
12 |   selection: '#343739',
13 |   selectionMatch: '#343739',
14 |   lineHighlight: '#343739',
15 | };
16 | 
17 | export default createTheme({
18 |   theme: 'dark',
19 |   settings: {
20 |     background: '#282b2e',
21 |     foreground: '#a9b7c6',
22 |     caret: '#00FF00',
23 |     selection: '#4e5254',
24 |     selectionMatch: '#4e5254',
25 |     gutterForeground: '#cccccc50',
26 |     lineHighlight: '#7f85891f',
27 |   },
28 |   styles: [
29 |     { tag: t.labelName, color: 'inherit' },
30 |     { tag: [t.keyword, t.deleted, t.className], color: '#a9b7c6' },
31 |     { tag: [t.number, t.literal], color: '#6897bb' },
32 |     //{ tag: [t.link, t.variableName], color: '#629755' },
33 |     { tag: [t.link, t.variableName], color: '#a9b7c6' },
34 |     { tag: [t.comment, t.quote], color: 'grey' },
35 |     { tag: [t.meta, t.documentMeta], color: '#bbb529' },
36 |     //{ tag: [t.string, t.propertyName, t.attributeValue], color: '#6a8759' },
37 |     { tag: [t.propertyName, t.attributeValue], color: '#a9b7c6' },
38 |     { tag: [t.string], color: '#6a8759' },
39 |     { tag: [t.heading, t.typeName], color: '#ffc66d' },
40 |     { tag: [t.attributeName], color: '#a9b7c6' },
41 |     { tag: [t.emphasis], fontStyle: 'italic' },
42 |   ],
43 | });
44 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/blackscreen.mjs:
--------------------------------------------------------------------------------
 1 | import { tags as t } from '@lezer/highlight';
 2 | import { createTheme } from './theme-helper.mjs';
 3 | export const settings = {
 4 |   background: 'black',
 5 |   foreground: 'white', // whats that?
 6 |   caret: 'white',
 7 |   selection: '#ffffff20',
 8 |   selectionMatch: '#036dd626',
 9 |   lineHighlight: '#ffffff10',
10 |   lineBackground: '#00000050',
11 |   gutterBackground: 'transparent',
12 |   gutterForeground: '#8a919966',
13 | };
14 | export default createTheme({
15 |   theme: 'dark',
16 |   settings,
17 |   styles: [
18 |     { tag: t.labelName, color: 'inherit' },
19 |     { tag: t.keyword, color: 'inherit' },
20 |     { tag: t.operator, color: 'inherit' },
21 |     { tag: t.special(t.variableName), color: 'inherit' },
22 |     { tag: t.typeName, color: 'inherit' },
23 |     { tag: t.atom, color: 'inherit' },
24 |     { tag: t.number, color: 'inherit' },
25 |     { tag: t.definition(t.variableName), color: 'inherit' },
26 |     { tag: t.string, color: 'inherit' },
27 |     { tag: t.special(t.string), color: 'inherit' },
28 |     { tag: t.comment, color: 'inherit' },
29 |     { tag: t.variableName, color: 'inherit' },
30 |     { tag: t.tagName, color: 'inherit' },
31 |     { tag: t.bracket, color: 'inherit' },
32 |     { tag: t.meta, color: 'inherit' },
33 |     { tag: t.attributeName, color: 'inherit' },
34 |     { tag: t.propertyName, color: 'inherit' },
35 |     { tag: t.className, color: 'inherit' },
36 |     { tag: t.invalid, color: 'inherit' },
37 |     { tag: [t.unit, t.punctuation], color: 'inherit' },
38 |   ],
39 | });
40 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/duotoneDark.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * duotone
 3 |  * author Bram de Haan
 4 |  * by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
 5 |  */
 6 | import { tags as t } from '@lezer/highlight';
 7 | import { createTheme } from './theme-helper.mjs';
 8 | 
 9 | export const settings = {
10 |   background: '#2a2734',
11 |   lineBackground: '#2a273499',
12 |   foreground: '#eeebff',
13 |   caret: '#ffad5c',
14 |   selection: 'rgba(255, 255, 255, 0.1)',
15 |   gutterBackground: '#2a2734',
16 |   gutterForeground: '#545167',
17 |   lineHighlight: '#36334280',
18 | };
19 | 
20 | export default createTheme({
21 |   theme: 'dark',
22 |   settings: {
23 |     background: '#2a2734',
24 |     foreground: '#6c6783',
25 |     caret: '#ffad5c',
26 |     selection: '#9a86fd',
27 |     selectionMatch: '#9a86fd',
28 |     gutterBackground: '#2a2734',
29 |     gutterForeground: '#545167',
30 |     lineHighlight: '#36334280',
31 |   },
32 |   styles: [
33 |     { tag: [t.comment, t.bracket, t.operator], color: '#6c6783' },
34 |     { tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#ffcc99' },
35 |     { tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.className, t.variableName], color: '#eeebff' },
36 |     { tag: [t.typeName, t.url], color: '#eeebff' },
37 |     { tag: t.string, color: '#ffb870' },
38 |     /* { tag: [t.propertyName], color: '#9a86fd' }, */
39 |     { tag: [t.propertyName], color: '#eeebff' },
40 |     { tag: t.labelName, color: '#eeebff' },
41 |   ],
42 | });
43 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/green-text.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Atom One
 3 |  * Atom One dark syntax theme
 4 |  *
 5 |  * https://github.com/atom/one-dark-syntax
 6 |  */
 7 | import { tags as t } from '@lezer/highlight';
 8 | import { createTheme } from './theme-helper.mjs';
 9 | 
10 | const hex = ['#000000', '#8ed675', '#56bd2a', '#54636D', '#171717'];
11 | 
12 | export const settings = {
13 |   background: hex[0],
14 |   lineBackground: 'transparent',
15 |   foreground: hex[2],
16 |   selection: hex[4],
17 |   selectionMatch: hex[0],
18 |   gutterBackground: hex[0],
19 |   gutterForeground: hex[3],
20 |   gutterBorder: 'transparent',
21 |   lineHighlight: hex[0],
22 | };
23 | 
24 | export default createTheme({
25 |   theme: 'dark',
26 |   settings,
27 |   styles: [
28 |     {
29 |       tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
30 |       color: hex[2],
31 |     },
32 |     { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
33 |     { tag: t.comment, color: hex[3] },
34 |     { tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
35 |     { tag: [t.attributeName, t.number], color: hex[1] },
36 |     { tag: t.keyword, color: hex[2] },
37 |     { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
38 |   ],
39 | });
40 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/red-text.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Atom One
 3 |  * Atom One dark syntax theme
 4 |  *
 5 |  * https://github.com/atom/one-dark-syntax
 6 |  */
 7 | import { tags as t } from '@lezer/highlight';
 8 | import { createTheme } from './theme-helper.mjs';
 9 | 
10 | const hex = ['#000000', '#ff5356', '#bd312a', '#54636D', '#171717'];
11 | 
12 | export const settings = {
13 |   background: hex[0],
14 |   lineBackground: 'transparent',
15 |   foreground: hex[2],
16 |   selection: hex[4],
17 |   selectionMatch: hex[0],
18 |   gutterBackground: hex[0],
19 |   gutterForeground: hex[3],
20 |   gutterBorder: 'transparent',
21 |   lineHighlight: hex[0],
22 | };
23 | 
24 | export default createTheme({
25 |   theme: 'dark',
26 |   settings,
27 |   styles: [
28 |     {
29 |       tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
30 |       color: hex[2],
31 |     },
32 |     { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
33 |     { tag: t.comment, color: hex[3] },
34 |     { tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
35 |     { tag: [t.attributeName, t.number], color: hex[1] },
36 |     { tag: t.keyword, color: hex[2] },
37 |     { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
38 |   ],
39 | });
40 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/sonic-pink.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 |  * Atom One
 3 |  * Atom One dark syntax theme
 4 |  *
 5 |  * https://github.com/atom/one-dark-syntax
 6 |  */
 7 | import { tags as t } from '@lezer/highlight';
 8 | import { createTheme } from './theme-helper.mjs';
 9 | 
10 | const hex = ['#1e1e1e', '#fbde2d', '#ff1493', '#4c83ff', '#ededed', '#cccccc', '#ffffff30', '#dc2f8c'];
11 | 
12 | export const settings = {
13 |   background: '#000000',
14 |   lineBackground: 'transparent',
15 |   foreground: hex[4],
16 |   selection: hex[6],
17 |   gutterBackground: hex[0],
18 |   gutterForeground: hex[5],
19 |   gutterBorder: 'transparent',
20 |   lineHighlight: hex[0],
21 | };
22 | 
23 | export default createTheme({
24 |   theme: 'dark',
25 |   settings,
26 |   styles: [
27 |     {
28 |       tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
29 |       color: hex[4],
30 |     },
31 |     { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[3] },
32 | 
33 |     { tag: t.comment, color: '#54636D' },
34 |     { tag: [t.variableName, t.propertyName, t.labelName], color: hex[4] },
35 |     { tag: [t.attributeName, t.number], color: hex[3] },
36 |     { tag: t.keyword, color: hex[1] },
37 |     { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] },
38 |   ],
39 | });
40 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/terminal.mjs:
--------------------------------------------------------------------------------
 1 | import { tags as t } from '@lezer/highlight';
 2 | import { createTheme } from './theme-helper.mjs';
 3 | export const settings = {
 4 |   background: 'black',
 5 |   foreground: '#41FF00', // whats that?
 6 |   caret: '#41FF00',
 7 |   selection: '#ffffff20',
 8 |   selectionMatch: '#036dd626',
 9 |   lineHighlight: '#ffffff10',
10 |   gutterBackground: 'transparent',
11 |   gutterForeground: '#8a919966',
12 | };
13 | export default createTheme({
14 |   theme: 'dark',
15 |   settings,
16 |   styles: [
17 |     { tag: t.labelName, color: 'inherit' },
18 |     { tag: t.keyword, color: 'inherit' },
19 |     { tag: t.operator, color: 'inherit' },
20 |     { tag: t.special(t.variableName), color: 'inherit' },
21 |     { tag: t.typeName, color: 'inherit' },
22 |     { tag: t.atom, color: 'inherit' },
23 |     { tag: t.number, color: 'inherit' },
24 |     { tag: t.definition(t.variableName), color: 'inherit' },
25 |     { tag: t.string, color: 'inherit' },
26 |     { tag: t.special(t.string), color: 'inherit' },
27 |     { tag: t.comment, color: 'inherit' },
28 |     { tag: t.variableName, color: 'inherit' },
29 |     { tag: t.tagName, color: 'inherit' },
30 |     { tag: t.bracket, color: 'inherit' },
31 |     { tag: t.meta, color: 'inherit' },
32 |     { tag: t.attributeName, color: 'inherit' },
33 |     { tag: t.propertyName, color: 'inherit' },
34 |     { tag: t.className, color: 'inherit' },
35 |     { tag: t.invalid, color: 'inherit' },
36 |   ],
37 | });
38 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/whitescreen.mjs:
--------------------------------------------------------------------------------
 1 | import { tags as t } from '@lezer/highlight';
 2 | import { createTheme } from './theme-helper.mjs';
 3 | export const settings = {
 4 |   background: 'white',
 5 |   foreground: 'black', // whats that?
 6 |   caret: 'black',
 7 |   selection: 'rgba(128, 203, 196, 0.5)',
 8 |   selectionMatch: '#ffffff26',
 9 |   lineHighlight: '#cccccc50',
10 |   lineBackground: '#ffffff50',
11 |   gutterBackground: 'transparent',
12 |   gutterForeground: 'black',
13 |   light: true,
14 | };
15 | export default createTheme({
16 |   theme: 'light',
17 |   settings,
18 |   styles: [
19 |     { tag: t.labelName, color: 'inherit' },
20 |     { tag: t.keyword, color: 'inherit' },
21 |     { tag: t.operator, color: 'inherit' },
22 |     { tag: t.special(t.variableName), color: 'inherit' },
23 |     { tag: t.typeName, color: 'inherit' },
24 |     { tag: t.atom, color: 'inherit' },
25 |     { tag: t.number, color: 'inherit' },
26 |     { tag: t.definition(t.variableName), color: 'inherit' },
27 |     { tag: t.string, color: 'inherit' },
28 |     { tag: t.special(t.string), color: 'inherit' },
29 |     { tag: t.comment, color: 'inherit' },
30 |     { tag: t.variableName, color: 'inherit' },
31 |     { tag: t.tagName, color: 'inherit' },
32 |     { tag: t.bracket, color: 'inherit' },
33 |     { tag: t.meta, color: 'inherit' },
34 |     { tag: t.attributeName, color: 'inherit' },
35 |     { tag: t.propertyName, color: 'inherit' },
36 |     { tag: t.className, color: 'inherit' },
37 |     { tag: t.invalid, color: 'inherit' },
38 |   ],
39 | });
40 | 


--------------------------------------------------------------------------------
/packages/codemirror/themes/xcodeLight.mjs:
--------------------------------------------------------------------------------
 1 | import { tags as t } from '@lezer/highlight';
 2 | import { createTheme } from './theme-helper.mjs';
 3 | 
 4 | export const settings = {
 5 |   light: true,
 6 |   background: '#fff',
 7 |   lineBackground: '#ffffff99',
 8 |   foreground: '#3D3D3D',
 9 |   selection: '#BBDFFF',
10 |   selectionMatch: '#BBDFFF',
11 |   gutterBackground: '#fff',
12 |   gutterForeground: '#AFAFAF',
13 |   lineHighlight: '#EDF4FF',
14 | };
15 | 
16 | export default createTheme({
17 |   theme: 'light',
18 |   settings: {
19 |     background: '#fff',
20 |     foreground: '#3D3D3D',
21 |     selection: '#BBDFFF',
22 |     selectionMatch: '#BBDFFF',
23 |     gutterBackground: '#fff',
24 |     gutterForeground: '#AFAFAF',
25 |     lineHighlight: '#d5e6ff69',
26 |   },
27 |   styles: [
28 |     { tag: [t.comment, t.quote], color: '#707F8D' },
29 |     { tag: [t.typeName, t.typeOperator], color: '#aa0d91' },
30 |     { tag: [t.keyword], color: '#aa0d91', fontWeight: 'bold' },
31 |     { tag: [t.string, t.meta], color: '#D23423' },
32 |     { tag: [t.name], color: '#032f62' },
33 |     { tag: [t.typeName], color: '#522BB2' },
34 |     { tag: [t.variableName], color: '#23575C' },
35 |     { tag: [t.definition(t.variableName)], color: '#327A9E' },
36 |     { tag: [t.regexp, t.link], color: '#0e0eff' },
37 |   ],
38 | });
39 | 


--------------------------------------------------------------------------------
/packages/codemirror/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/core/.npmignore:
--------------------------------------------------------------------------------
1 | examples


--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/core
 2 | 
 3 | This package contains the bare essence of strudel.
 4 | 
 5 | ## Install
 6 | 
 7 | ```sh
 8 | npm i @strudel/core --save
 9 | ```
10 | 
11 | ## Example
12 | 
13 | ```js
14 | import { sequence } from '@strudel/core';
15 | 
16 | const pattern = sequence('a', ['b', 'c']);
17 | 
18 | const events = pattern.queryArc(0, 1);
19 | 
20 | const spans = events.map(
21 |   (event) => `${event.value}: ${event.whole.begin.toFraction()} - ${event.whole.end.toFraction()} `,
22 | );
23 | ```
24 | 
25 | yields:
26 | 
27 | ```log
28 | a: 0 - 1/2
29 | b: 1/2 - 3/4
30 | c: 3/4 - 1
31 | a: 1 - 3/2
32 | b: 3/2 - 7/4
33 | c: 7/4 - 2
34 | ```
35 | 
36 | - [play with @strudel/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
37 | - [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html)
38 | - [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
39 | - [open minimal vite example](./examples/vite-vanilla-repl/)


--------------------------------------------------------------------------------
/packages/core/bench/pattern.bench.mjs:
--------------------------------------------------------------------------------
 1 | import { describe, bench } from 'vitest';
 2 | 
 3 | import { calculateTactus, sequence, stack } from '../index.mjs';
 4 | 
 5 | const pat64 = sequence(...Array(64).keys());
 6 | 
 7 | describe('steps', () => {
 8 |   calculateTactus(true);
 9 |   bench(
10 |     '+tactus',
11 |     () => {
12 |       pat64.iter(64).fast(64).firstCycle();
13 |     },
14 |     { time: 1000 },
15 |   );
16 | 
17 |   calculateTactus(false);
18 |   bench(
19 |     '-tactus',
20 |     () => {
21 |       pat64.iter(64).fast(64).firstCycle();
22 |     },
23 |     { time: 1000 },
24 |   );
25 | });
26 | 
27 | describe('stack', () => {
28 |   calculateTactus(true);
29 |   bench(
30 |     '+tactus',
31 |     () => {
32 |       stack(pat64, pat64, pat64, pat64, pat64, pat64, pat64, pat64).fast(64).firstCycle();
33 |     },
34 |     { time: 1000 },
35 |   );
36 | 
37 |   calculateTactus(false);
38 |   bench(
39 |     '-tactus',
40 |     () => {
41 |       stack(pat64, pat64, pat64, pat64, pat64, pat64, pat64, pat64).fast(64).firstCycle();
42 |     },
43 |     { time: 1000 },
44 |   );
45 | });
46 | calculateTactus(true);
47 | 


--------------------------------------------------------------------------------
/packages/core/logger.mjs:
--------------------------------------------------------------------------------
 1 | export const logKey = 'strudel.log';
 2 | 
 3 | let debounce = 1000,
 4 |   lastMessage,
 5 |   lastTime;
 6 | 
 7 | export function logger(message, type, data = {}) {
 8 |   let t = performance.now();
 9 |   if (lastMessage === message && t - lastTime < debounce) {
10 |     return;
11 |   }
12 |   lastMessage = message;
13 |   lastTime = t;
14 |   console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px');
15 |   if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') {
16 |     document.dispatchEvent(
17 |       new CustomEvent(logKey, {
18 |         detail: {
19 |           message,
20 |           type,
21 |           data,
22 |         },
23 |       }),
24 |     );
25 |   }
26 | }
27 | 
28 | logger.key = logKey;
29 | 


--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/core",
 3 |   "version": "1.2.2",
 4 |   "description": "Port of Tidal Cycles to JavaScript",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "test": "vitest run",
12 |     "bench": "vitest bench",
13 |     "build": "vite build",
14 |     "prepublishOnly": "pnpm build"
15 |   },
16 |   "repository": {
17 |     "type": "git",
18 |     "url": "git+https://github.com/tidalcycles/strudel.git"
19 |   },
20 |   "keywords": [
21 |     "tidalcycles",
22 |     "strudel",
23 |     "pattern",
24 |     "livecoding",
25 |     "algorave"
26 |   ],
27 |   "author": "Alex McLean  (https://slab.org)",
28 |   "license": "AGPL-3.0-or-later",
29 |   "bugs": {
30 |     "url": "https://github.com/tidalcycles/strudel/issues"
31 |   },
32 |   "homepage": "https://strudel.cc",
33 |   "dependencies": {
34 |     "fraction.js": "^5.2.1"
35 |   },
36 |   "gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
37 |   "devDependencies": {
38 |     "vite": "^6.0.11",
39 |     "vitest": "^3.0.4"
40 |   }
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/core/state.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 | state.mjs - 
 3 | Copyright (C) 2022 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | export class State {
 8 |   constructor(span, controls = {}) {
 9 |     this.span = span;
10 |     this.controls = controls;
11 |   }
12 | 
13 |   // Returns new State with different span
14 |   setSpan(span) {
15 |     return new State(span, this.controls);
16 |   }
17 | 
18 |   withSpan(func) {
19 |     return this.setSpan(func(this.span));
20 |   }
21 | 
22 |   // Returns new State with different controls
23 |   setControls(controls) {
24 |     return new State(this.span, controls);
25 |   }
26 | }
27 | 
28 | export default State;
29 | 


--------------------------------------------------------------------------------
/packages/core/test/fraction.test.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 | fraction.test.mjs - 
 3 | Copyright (C) 2022 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | import Fraction, { gcd } from '../fraction.mjs';
 8 | import { describe, it, expect } from 'vitest';
 9 | 
10 | describe('gcd', () => {
11 |   it('should work', () => {
12 |     const F = Fraction._original;
13 |     expect(gcd(F(1 / 6), F(1 / 4)).toFraction()).toEqual('1/12');
14 |   });
15 | });
16 | 


--------------------------------------------------------------------------------
/packages/core/test/solmization.test.js:
--------------------------------------------------------------------------------
 1 | /*test for issue 302 support alternative solmization types */
 2 | import { sol2note } from '../util.mjs';
 3 | import { test } from 'vitest';
 4 | import assert from 'assert';
 5 | 
 6 | test('solmization - letters', () => {
 7 |   const result = sol2note(60, 'letters');
 8 |   const expected = 'C4';
 9 |   assert.equal(result, expected);
10 | });
11 | 
12 | test('solmization - solfeggio', () => {
13 |   const result = sol2note(60, 'solfeggio');
14 |   const expected = 'Do4';
15 |   assert.equal(result, expected);
16 | });
17 | 
18 | test('solmization - indian', () => {
19 |   const result = sol2note(60, 'indian');
20 |   const expected = 'Sa4';
21 |   assert.equal(result, expected);
22 | });
23 | 
24 | test('solmization - german', () => {
25 |   const result = sol2note(60, 'german');
26 |   const expected = 'C4';
27 |   assert.equal(result, expected);
28 | });
29 | 
30 | test('solmization - byzantine', () => {
31 |   const result = sol2note(60, 'byzantine');
32 |   const expected = 'Ni4';
33 |   assert.equal(result, expected);
34 | });
35 | 
36 | test('solmization - japanese', () => {
37 |   const result = sol2note(60, 'japanese');
38 |   const expected = 'I4';
39 |   assert.equal(result, expected);
40 | });
41 | 


--------------------------------------------------------------------------------
/packages/core/time.mjs:
--------------------------------------------------------------------------------
 1 | let time;
 2 | export function getTime() {
 3 |   if (!time) {
 4 |     throw new Error('no time set! use setTime to define a time source');
 5 |   }
 6 |   return time();
 7 | }
 8 | 
 9 | export function setTime(func) {
10 |   time = func;
11 | }
12 | 


--------------------------------------------------------------------------------
/packages/core/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   base: './',
 8 |   plugins: [],
 9 |   build: {
10 |     lib: {
11 |       entry: resolve(__dirname, 'index.mjs'),
12 |       formats: ['es'],
13 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
14 |     },
15 |     rollupOptions: {
16 |       external: [...Object.keys(dependencies)],
17 |     },
18 |     target: 'esnext',
19 |   },
20 | });
21 | 


--------------------------------------------------------------------------------
/packages/csound/README.md:
--------------------------------------------------------------------------------
1 | # @strudel/csound
2 | 


--------------------------------------------------------------------------------
/packages/csound/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/csound",
 3 |   "version": "1.2.3",
 4 |   "description": "csound bindings for strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "tidalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Felix Roos ",
26 |   "contributors": [
27 |     "Alex McLean "
28 |   ],
29 |   "license": "AGPL-3.0-or-later",
30 |   "bugs": {
31 |     "url": "https://github.com/tidalcycles/strudel/issues"
32 |   },
33 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
34 |   "dependencies": {
35 |     "@csound/browser": "6.18.7",
36 |     "@strudel/core": "workspace:*",
37 |     "@strudel/webaudio": "workspace:*"
38 |   },
39 |   "devDependencies": {
40 |     "vite": "^6.0.11"
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/packages/csound/project.csd:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | sr=48000
 5 | ksmps=64
 6 | nchnls=2
 7 | 0dbfs=1
 8 | 
 9 | 
10 | 


--------------------------------------------------------------------------------
/packages/csound/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/desktopbridge/README.md:
--------------------------------------------------------------------------------
1 | # @strudel/desktopbridge
2 | 
3 | This package contains utilities used to communicate with the Tauri backend


--------------------------------------------------------------------------------
/packages/desktopbridge/index.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 | index.mjs - 
 3 | Copyright (C) 2022 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | export * from './midibridge.mjs';
 8 | export * from './utils.mjs';
 9 | export * from './oscbridge.mjs';
10 | 


--------------------------------------------------------------------------------
/packages/desktopbridge/loggerbridge.mjs:
--------------------------------------------------------------------------------
 1 | import { listen } from '@tauri-apps/api/event';
 2 | import { logger } from '../core/logger.mjs';
 3 | 
 4 | // listen for log events from the Tauri backend and log in the UI
 5 | await listen('log-event', (e) => {
 6 |   if (e.payload == null) {
 7 |     return;
 8 |   }
 9 |   const { message, message_type } = e.payload;
10 |   logger(message, message_type);
11 | });
12 | 


--------------------------------------------------------------------------------
/packages/desktopbridge/oscbridge.mjs:
--------------------------------------------------------------------------------
 1 | import { Pattern, ClockCollator } from '@strudel/core';
 2 | import { parseControlsFromHap } from 'node_modules/@strudel/osc/osc.mjs';
 3 | import { Invoke } from './utils.mjs';
 4 | 
 5 | const collator = new ClockCollator({});
 6 | 
 7 | export async function oscTriggerTauri(t_deprecate, hap, currentTime, cps = 1, targetTime) {
 8 |   const controls = parseControlsFromHap(hap, cps);
 9 |   const params = [];
10 |   const timestamp = collator.calculateTimestamp(currentTime, targetTime);
11 | 
12 |   Object.keys(controls).forEach((key) => {
13 |     const val = controls[key];
14 |     const value = typeof val === 'number' ? val.toString() : val;
15 | 
16 |     if (value == null) {
17 |       return;
18 |     }
19 |     params.push({
20 |       name: key,
21 |       value,
22 |       valueisnumber: typeof val === 'number',
23 |     });
24 |   });
25 | 
26 |   if (params.length === 0) {
27 |     return;
28 |   }
29 |   const message = { target: '/dirt/play', timestamp, params };
30 |   setTimeout(() => {
31 |     Invoke('sendosc', { messagesfromjs: [message] });
32 |   });
33 | }
34 | Pattern.prototype.osc = function () {
35 |   return this.onTrigger(oscTriggerTauri);
36 | };
37 | 


--------------------------------------------------------------------------------
/packages/desktopbridge/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/desktopbridge",
 3 |   "version": "0.1.0",
 4 |   "private": true,
 5 |   "description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app",
 6 |   "main": "index.mjs",
 7 |   "type": "module",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "git+https://github.com/tidalcycles/strudel.git"
11 |   },
12 |   "keywords": [
13 |     "tidalcycles",
14 |     "strudel",
15 |     "pattern",
16 |     "livecoding",
17 |     "algorave"
18 |   ],
19 |   "author": "Jade Rowland ",
20 |   "license": "AGPL-3.0-or-later",
21 |   "bugs": {
22 |     "url": "https://github.com/tidalcycles/strudel/issues"
23 |   },
24 |   "dependencies": {
25 |     "@strudel/core": "workspace:*",
26 |     "@tauri-apps/api": "^2.2.0"
27 |   },
28 |   "homepage": "https://github.com/tidalcycles/strudel#readme"
29 | }


--------------------------------------------------------------------------------
/packages/desktopbridge/utils.mjs:
--------------------------------------------------------------------------------
1 | import { invoke } from '@tauri-apps/api/core';
2 | 
3 | export const Invoke = invoke;
4 | export const isTauri = () => window.__TAURI_IPC__ != null;
5 | 


--------------------------------------------------------------------------------
/packages/draw/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/canvas
 2 | 
 3 | Helpers for drawing with the Canvas API and Strudel
 4 | 
 5 | ## Install
 6 | 
 7 | ```sh
 8 | npm i @strudel/canvas --save
 9 | ```
10 | 


--------------------------------------------------------------------------------
/packages/draw/index.mjs:
--------------------------------------------------------------------------------
1 | export * from './animate.mjs';
2 | export * from './color.mjs';
3 | export * from './draw.mjs';
4 | export * from './pianoroll.mjs';
5 | export * from './spiral.mjs';
6 | export * from './pitchwheel.mjs';
7 | 


--------------------------------------------------------------------------------
/packages/draw/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/draw",
 3 |   "version": "1.2.2",
 4 |   "description": "Helpers for drawing with Strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "titdalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Felix Roos ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
31 |   "dependencies": {
32 |     "@strudel/core": "workspace:*"
33 |   },
34 |   "devDependencies": {
35 |     "vite": "^6.0.11"
36 |   }
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/draw/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/embed/embed.js:
--------------------------------------------------------------------------------
 1 | class Strudel extends HTMLElement {
 2 |   constructor() {
 3 |     super();
 4 |   }
 5 |   connectedCallback() {
 6 |     setTimeout(() => {
 7 |       const code = this.getAttribute('code') || (this.innerHTML + '').replace('', '').trim();
 8 |       const iframe = document.createElement('iframe');
 9 |       const src = `https://strudel.cc/#${encodeURIComponent(btoa(code))}`;
10 |       // const src = `http://localhost:3000/#${encodeURIComponent(btoa(code))}`;
11 |       iframe.setAttribute('src', src);
12 |       iframe.setAttribute('width', '600');
13 |       iframe.setAttribute('height', '400');
14 |       this.appendChild(iframe);
15 |     });
16 |   }
17 | }
18 | customElements.define('strudel-repl', Strudel);
19 | 


--------------------------------------------------------------------------------
/packages/embed/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/embed",
 3 |   "version": "1.1.0",
 4 |   "description": "Embeddable Web Component to load a Strudel REPL into an iframe",
 5 |   "main": "embed.js",
 6 |   "type": "module",
 7 |   "repository": {
 8 |     "type": "git",
 9 |     "url": "git+https://github.com/tidalcycles/strudel.git"
10 |   },
11 |   "keywords": [
12 |     "tidalcycles",
13 |     "strudel",
14 |     "pattern",
15 |     "livecoding",
16 |     "algorave"
17 |   ],
18 |   "author": "Felix Roos ",
19 |   "license": "AGPL-3.0-or-later",
20 |   "bugs": {
21 |     "url": "https://github.com/tidalcycles/strudel/issues"
22 |   },
23 |   "homepage": "https://github.com/tidalcycles/strudel#readme"
24 | }
25 | 


--------------------------------------------------------------------------------
/packages/gamepad/index.mjs:
--------------------------------------------------------------------------------
1 | import './gamepad.mjs';
2 | 
3 | export * from './gamepad.mjs';
4 | 


--------------------------------------------------------------------------------
/packages/gamepad/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/gamepad",
 3 |   "version": "1.2.2",
 4 |   "description": "Gamepad Inputs for strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "titdalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Yuta Nakayama ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
31 |   "dependencies": {
32 |     "@strudel/core": "workspace:*"
33 |   },
34 |   "devDependencies": {
35 |     "vite": "^6.0.11"
36 |   }
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/gamepad/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/hs2js/.npmignore:
--------------------------------------------------------------------------------
1 | pnpm-lock.yaml
2 | vite.config.js
3 | 


--------------------------------------------------------------------------------
/packages/hs2js/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "hs2js",
 3 |   "version": "0.2.0",
 4 |   "private": true,
 5 |   "description": "Experimental Haskell in JavaScript interpreter",
 6 |   "main": "src/index.mjs",
 7 |   "type": "module",
 8 |   "publishConfig": {
 9 |     "main": "dist/index.js",
10 |     "module": "dist/index.mjs"
11 |   },
12 |   "scripts": {
13 |     "build-wasm": "tree-sitter build-wasm node_modules/tree-sitter-haskell && mv tree-sitter-haskell.wasm ./dist/ && cp node_modules/web-tree-sitter/tree-sitter.wasm ./dist/",
14 |     "build": "vite build && npm run build-wasm",
15 |     "prepublishOnly": "npm run build"
16 |   },
17 |   "repository": {
18 |     "type": "git",
19 |     "url": "git+https://github.com/tidalcycles/strudel.git"
20 |   },
21 |   "keywords": [
22 |     "haskell",
23 |     "javascript"
24 |   ],
25 |   "author": "Felix Roos ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel",
31 |   "dependencies": {
32 |     "web-tree-sitter": "^0.24.7"
33 |   },
34 |   "devDependencies": {
35 |     "tree-sitter-haskell": "^0.23.1",
36 |     "vite": "^6.0.11"
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/packages/hs2js/src/index.mjs:
--------------------------------------------------------------------------------
1 | export * from './hs2js.mjs';
2 | export * from './parser.mjs';
3 | 


--------------------------------------------------------------------------------
/packages/hs2js/src/parser.mjs:
--------------------------------------------------------------------------------
 1 | import Parser from 'web-tree-sitter';
 2 | 
 3 | let base = '/';
 4 | export function setBase(path) {
 5 |   base = path;
 6 | }
 7 | 
 8 | let isReady = false,
 9 |   parser;
10 | async function _loadParser() {
11 |   await Parser.init({
12 |     locateFile(scriptName, scriptDirectory) {
13 |       return `${base}${scriptName}`;
14 |     },
15 |   });
16 |   parser = new Parser();
17 |   const Lang = await Parser.Language.load(`${base}tree-sitter-haskell.wasm`);
18 |   parser.setLanguage(Lang);
19 |   isReady = true;
20 |   return parser;
21 | }
22 | 
23 | let parserLoaded;
24 | export function loadParser() {
25 |   if (!parserLoaded) {
26 |     parserLoaded = _loadParser();
27 |   }
28 |   return parserLoaded;
29 | }
30 | 
31 | export function parse(code) {
32 |   if (!isReady) {
33 |     throw new Error('hs2js not ready. await loadParser before calling evaluate or parse functions');
34 |   }
35 |   // for some reason, the parser doesn't like new lines..
36 |   return parser.parse(code.replaceAll('\n\n', '~~~~').replaceAll('\n', ' ').replaceAll('~~~~', ' \n'));
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/hs2js/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { resolve } from 'path';
 3 | 
 4 | // https://vitejs.dev/config/
 5 | export default defineConfig({
 6 |   plugins: [],
 7 |   build: {
 8 |     lib: {
 9 |       entry: resolve(__dirname, 'src', 'index.mjs'),
10 |       name: 'hs2js',
11 |       formats: ['es', 'iife'],
12 |       fileName: (ext) => ({ es: 'index.mjs', iife: 'index.js' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       // external: [...Object.keys(dependencies)],
16 |       plugins: [],
17 |     },
18 |     target: 'esnext',
19 |   },
20 | });
21 | 


--------------------------------------------------------------------------------
/packages/hydra/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/hydra
 2 | 
 3 | This package integrates [hydra-synth](https://www.npmjs.com/package/hydra-synth) into strudel.
 4 | 
 5 | ## Usage in Strudel
 6 | 
 7 | This package is imported into strudel by default. To activate Hydra, place this code at the top of your code:
 8 | 
 9 | ```js
10 | await initHydra();
11 | ```
12 | 
13 | Then you can use hydra below!
14 | 
15 | ### options
16 | 
17 | You can also pass options to the `initHydra` function. These can be used to set [hydra options](https://github.com/hydra-synth/hydra-synth#api) + these strudel specific options:
18 | 
19 | - `feedStrudel`: sends the strudel canvas to `s0`. The strudel canvas is used to draw `pianoroll`, `spiral`, `scope` etc..
20 | 
21 | ## Usage via npm
22 | 
23 | ```sh
24 | npm i @strudel/hydra
25 | ```
26 | 
27 | Then add the import to your evalScope:
28 | 
29 | ```js
30 | import { evalScope } from '@strudel/core';
31 | 
32 | evalScope(
33 |   import('@strudel/hydra')
34 | )
35 | ```
36 | 


--------------------------------------------------------------------------------
/packages/hydra/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/hydra",
 3 |   "version": "1.2.2",
 4 |   "description": "Hydra integration for strudel",
 5 |   "main": "hydra.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "server": "node server.js",
12 |     "tidal-sniffer": "node tidal-sniffer.js",
13 |     "client": "npx serve -p 4321",
14 |     "build-bin": "npx pkg server.js --targets node16-macos-x64,node16-win-x64,node16-linux-x64 --out-path bin",
15 |     "build": "vite build",
16 |     "prepublishOnly": "npm run build"
17 |   },
18 |   "repository": {
19 |     "type": "git",
20 |     "url": "git+https://github.com/tidalcycles/strudel.git"
21 |   },
22 |   "keywords": [
23 |     "tidalcycles",
24 |     "strudel",
25 |     "pattern",
26 |     "livecoding",
27 |     "algorave"
28 |   ],
29 |   "author": "Felix Roos ",
30 |   "license": "AGPL-3.0-or-later",
31 |   "bugs": {
32 |     "url": "https://github.com/tidalcycles/strudel/issues"
33 |   },
34 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
35 |   "dependencies": {
36 |     "@strudel/core": "workspace:*",
37 |     "@strudel/draw": "workspace:*",
38 |     "hydra-synth": "^1.3.29"
39 |   },
40 |   "devDependencies": {
41 |     "pkg": "^5.8.1",
42 |     "vite": "^6.0.11"
43 |   }
44 | }
45 | 


--------------------------------------------------------------------------------
/packages/hydra/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'hydra.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/midi/index.mjs:
--------------------------------------------------------------------------------
1 | import './midi.mjs';
2 | 
3 | export * from './midi.mjs';
4 | 


--------------------------------------------------------------------------------
/packages/midi/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/midi",
 3 |   "version": "1.2.3",
 4 |   "description": "Midi API for strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "titdalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Felix Roos ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
31 |   "dependencies": {
32 |     "@strudel/core": "workspace:*",
33 |     "@strudel/webaudio": "workspace:*",
34 |     "webmidi": "^3.1.12"
35 |   },
36 |   "devDependencies": {
37 |     "vite": "^6.0.11"
38 |   }
39 | }
40 | 


--------------------------------------------------------------------------------
/packages/midi/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/mini/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/mini
 2 | 
 3 | This package contains the mini notation parser and pattern generator.
 4 | 
 5 | ## Install
 6 | 
 7 | ```sh
 8 | npm i @strudel/mini --save
 9 | ```
10 | 
11 | ## Example
12 | 
13 | ```js
14 | import { mini } from '@strudel/mini';
15 | 
16 | const pattern = mini('a [b c*2]');
17 | 
18 | const events = pattern.firstCycle().map((e) => e.show());
19 | console.log(events);
20 | ```
21 | 
22 | yields:
23 | 
24 | ```log
25 | (0/1 -> 1/2, 0/1 -> 1/2, a)
26 | (1/2 -> 3/4, 1/2 -> 3/4, b)
27 | (3/4 -> 7/8, 3/4 -> 7/8, c)
28 | (7/8 -> 1/1, 7/8 -> 1/1, c)
29 | ```
30 | 
31 | [Play with @strudel/mini codesandbox](https://codesandbox.io/s/strudel-mini-example-oe9wcu?file=/src/index.js)
32 | 
33 | ## Mini Notation API
34 | 
35 | See "Mini Notation" in the [Strudel Tutorial](https://strudel.cc/learn/mini-notation)
36 | 
37 | ## Building the Parser
38 | 
39 | The parser [krill-parser.js] is generated from [krill.pegjs](./krill.pegjs) using [peggy](https://peggyjs.org/).
40 | To generate the parser, run
41 | 
42 | ```js
43 | npm run build:parser
44 | ```
45 | 


--------------------------------------------------------------------------------
/packages/mini/bench/mini.bench.mjs:
--------------------------------------------------------------------------------
 1 | import { describe, bench } from 'vitest';
 2 | 
 3 | import { calculateTactus } from '../../core/index.mjs';
 4 | import { mini } from '../index.mjs';
 5 | 
 6 | describe('mini', () => {
 7 |   calculateTactus(true);
 8 |   bench(
 9 |     '+tactus',
10 |     () => {
11 |       mini('a b c*3 [c d e, f g] ').fast(64).firstCycle();
12 |     },
13 |     { time: 1000 },
14 |   );
15 | 
16 |   calculateTactus(false);
17 |   bench(
18 |     '-tactus',
19 |     () => {
20 |       mini('a b c*3 [c d e, f g] ').fast(64).firstCycle();
21 |     },
22 |     { time: 1000 },
23 |   );
24 |   calculateTactus(true);
25 | });
26 | 


--------------------------------------------------------------------------------
/packages/mini/index.mjs:
--------------------------------------------------------------------------------
1 | export * from './mini.mjs';
2 | export * from './krill-parser.js';
3 | 


--------------------------------------------------------------------------------
/packages/mini/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/mini",
 3 |   "version": "1.2.2",
 4 |   "description": "Mini notation for strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "test": "vitest run",
12 |     "bench": "vitest bench",
13 |     "build:parser": "peggy -o krill-parser.js --format es ./krill.pegjs",
14 |     "build": "vite build",
15 |     "prepublishOnly": "npm run build"
16 |   },
17 |   "repository": {
18 |     "type": "git",
19 |     "url": "git+https://github.com/tidalcycles/strudel.git"
20 |   },
21 |   "keywords": [
22 |     "tidalcycles",
23 |     "strudel",
24 |     "pattern",
25 |     "livecoding",
26 |     "algorave"
27 |   ],
28 |   "author": "Felix Roos ",
29 |   "license": "AGPL-3.0-or-later",
30 |   "bugs": {
31 |     "url": "https://github.com/tidalcycles/strudel/issues"
32 |   },
33 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
34 |   "dependencies": {
35 |     "@strudel/core": "workspace:*"
36 |   },
37 |   "devDependencies": {
38 |     "peggy": "^4.2.0",
39 |     "vite": "^6.0.11",
40 |     "vitest": "^3.0.4"
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/packages/mini/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/motion/index.mjs:
--------------------------------------------------------------------------------
1 | import './motion.mjs';
2 | 
3 | export * from './motion.mjs';
4 | 


--------------------------------------------------------------------------------
/packages/motion/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/motion",
 3 |   "version": "1.2.2",
 4 |   "description": "DeviceMotion API for strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "titdalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Yuta Nakayama ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
31 |   "dependencies": {
32 |     "@strudel/core": "workspace:*"
33 |   },
34 |   "devDependencies": {
35 |     "vite": "^6.0.11"
36 |   }
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/motion/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/mqtt/README.md:
--------------------------------------------------------------------------------
1 | # @strudel/serial
2 | 
3 | This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers.
4 | 


--------------------------------------------------------------------------------
/packages/mqtt/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/mqtt",
 3 |   "version": "1.2.2",
 4 |   "description": "MQTT API for strudel",
 5 |   "main": "mqtt.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "titdalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Alex McLean ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
31 |   "dependencies": {
32 |     "@strudel/core": "workspace:*",
33 |     "paho-mqtt": "^1.1.0"
34 |   },
35 |   "devDependencies": {
36 |     "vite": "^6.0.11"
37 |   }
38 | }
39 | 


--------------------------------------------------------------------------------
/packages/mqtt/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'mqtt.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/osc/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/osc
 2 | 
 3 | OSC output for strudel patterns! Currently only tested with super collider / super dirt.
 4 | 
 5 | ## Usage
 6 | 
 7 | OSC will only work if you run the REPL locally + the OSC server besides it:
 8 | 
 9 | From the project root:
10 | 
11 | ```js
12 | npm run repl
13 | ```
14 | 
15 | and in a seperate shell:
16 | 
17 | ```js
18 | npm run osc
19 | ```
20 | 
21 | This should give you
22 | 
23 | ```log
24 | osc client running on port 57120
25 | osc server running on port 57121
26 | websocket server running on port 8080
27 | ```
28 | 
29 | Now open Supercollider (with the super dirt startup file)
30 | 
31 | Now open the REPL and type:
32 | 
33 | ```js
34 | s(" hh").osc()
35 | ```
36 | 
37 | or just [click here](https://strudel.cc/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
38 | 
39 | You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.cc/learn/input-output/#superdirt-api)
40 | 


--------------------------------------------------------------------------------
/packages/osc/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
23 | 


--------------------------------------------------------------------------------
/packages/osc/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/osc",
 3 |   "version": "1.2.2",
 4 |   "description": "OSC messaging for strudel",
 5 |   "main": "osc.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "server": "node server.js",
12 |     "tidal-sniffer": "node tidal-sniffer.js",
13 |     "client": "npx serve -p 4321",
14 |     "build-bin": "npx pkg server.js --targets node16-macos-x64,node16-win-x64,node16-linux-x64 --out-path bin",
15 |     "build": "vite build",
16 |     "prepublishOnly": "npm run build"
17 |   },
18 |   "repository": {
19 |     "type": "git",
20 |     "url": "git+https://github.com/tidalcycles/strudel.git"
21 |   },
22 |   "keywords": [
23 |     "tidalcycles",
24 |     "strudel",
25 |     "pattern",
26 |     "livecoding",
27 |     "algorave"
28 |   ],
29 |   "author": "Felix Roos ",
30 |   "contributors": [
31 |     "Alex McLean "
32 |   ],
33 |   "license": "AGPL-3.0-or-later",
34 |   "bugs": {
35 |     "url": "https://github.com/tidalcycles/strudel/issues"
36 |   },
37 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
38 |   "dependencies": {
39 |     "@strudel/core": "workspace:*",
40 |     "osc-js": "^2.4.1"
41 |   },
42 |   "devDependencies": {
43 |     "pkg": "^5.8.1",
44 |     "vite": "^6.0.11"
45 |   }
46 | }
47 | 


--------------------------------------------------------------------------------
/packages/osc/superdirtoutput.js:
--------------------------------------------------------------------------------
 1 | import { oscTriggerTauri } from '../desktopbridge/oscbridge.mjs';
 2 | import { isTauri } from '../desktopbridge/utils.mjs';
 3 | import { oscTrigger } from './osc.mjs';
 4 | 
 5 | const trigger = isTauri() ? oscTriggerTauri : oscTrigger;
 6 | 
 7 | export const superdirtOutput = (hap, deadline, hapDuration, cps, targetTime) => {
 8 |   const currentTime = performance.now() / 1000;
 9 |   return trigger(null, hap, currentTime, cps, targetTime);
10 | };
11 | 


--------------------------------------------------------------------------------
/packages/osc/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'osc.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/reference/README.md:
--------------------------------------------------------------------------------
1 | # @strudel/reference
2 | 
3 | this package contains metadata for all documented strudel functions, useful to implement a reference.
4 | 
5 | ```js
6 | import { reference } from '@strudel/reference';
7 | console.log(reference)
8 | ```
9 | 


--------------------------------------------------------------------------------
/packages/reference/index.mjs:
--------------------------------------------------------------------------------
1 | import jsdoc from '../../doc.json';
2 | export const reference = jsdoc;
3 | 


--------------------------------------------------------------------------------
/packages/reference/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/reference",
 3 |   "version": "1.2.0",
 4 |   "description": "Headless reference of all strudel functions",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "tidalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Felix Roos ",
26 |   "contributors": [
27 |     "Alex McLean "
28 |   ],
29 |   "license": "AGPL-3.0-or-later",
30 |   "bugs": {
31 |     "url": "https://github.com/tidalcycles/strudel/issues"
32 |   },
33 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
34 |   "devDependencies": {
35 |     "vite": "^6.0.11"
36 |   }
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/reference/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { resolve } from 'path';
 3 | 
 4 | // https://vitejs.dev/config/
 5 | export default defineConfig({
 6 |   plugins: [],
 7 |   build: {
 8 |     lib: {
 9 |       entry: resolve(__dirname, 'index.mjs'),
10 |       formats: ['es'],
11 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
12 |     },
13 |     target: 'esnext',
14 |   },
15 | });
16 | 


--------------------------------------------------------------------------------
/packages/repl/.gitignore:
--------------------------------------------------------------------------------
1 | stats.html


--------------------------------------------------------------------------------
/packages/repl/index.mjs:
--------------------------------------------------------------------------------
1 | export * from './repl-component.mjs';
2 | export * from './prebake.mjs';
3 | 


--------------------------------------------------------------------------------
/packages/repl/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { resolve } from 'path';
 3 | import replace from '@rollup/plugin-replace';
 4 | import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
 5 | 
 6 | // https://vitejs.dev/config/
 7 | export default defineConfig({
 8 |   base: './',
 9 |   plugins: [bundleAudioWorkletPlugin()],
10 |   build: {
11 |     lib: {
12 |       entry: resolve(__dirname, 'index.mjs'),
13 |       name: 'strudel',
14 |       formats: ['es', 'iife'],
15 |       fileName: (ext) => ({ es: 'index.mjs', iife: 'index.js' })[ext],
16 |     },
17 |     rollupOptions: {
18 |       // external: [...Object.keys(dependencies)],
19 |       plugins: [
20 |         replace({
21 |           'process.env.NODE_ENV': JSON.stringify('production'),
22 |           preventAssignment: true,
23 |         }),
24 |       ],
25 |     },
26 |     target: 'esnext',
27 |   },
28 | });
29 | 


--------------------------------------------------------------------------------
/packages/sampler/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/sampler
 2 | 
 3 | This package allows you to serve your samples on disk to the strudel REPL. 
 4 | 
 5 | ```sh
 6 | cd ~/your/samples/ 
 7 | npx @strudel/sampler
 8 | ```
 9 | 
10 | This will run a server on `http://localhost:5432`. 
11 | You can now load the samples via:
12 | 
13 | ```js
14 | samples('http://localhost:5432')
15 | ```
16 | 
17 | ## Options
18 | 
19 | ```sh
20 | LOG=1 npx @strudel/sampler # adds logging
21 | PORT=5555 npx @strudel/sampler # changes port
22 | ```
23 | 


--------------------------------------------------------------------------------
/packages/sampler/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/sampler",
 3 |   "version": "0.2.0",
 4 |   "description": "",
 5 |   "keywords": [
 6 |     "tidalcycles",
 7 |     "strudel",
 8 |     "pattern",
 9 |     "livecoding",
10 |     "algorave"
11 |   ],
12 |   "author": "Felix Roos ",
13 |   "license": "AGPL-3.0-or-later",
14 |   "bin": "./sample-server.mjs",
15 |   "type": "module",
16 |   "dependencies": {
17 |     "cowsay": "^1.6.0"
18 |   }
19 | }
20 | 


--------------------------------------------------------------------------------
/packages/serial/README.md:
--------------------------------------------------------------------------------
1 | # @strudel/serial
2 | 
3 | This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers.
4 | 


--------------------------------------------------------------------------------
/packages/serial/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/serial",
 3 |   "version": "1.2.2",
 4 |   "description": "Webserial API for strudel",
 5 |   "main": "serial.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "titdalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Alex McLean ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
31 |   "dependencies": {
32 |     "@strudel/core": "workspace:*"
33 |   },
34 |   "devDependencies": {
35 |     "vite": "^6.0.11"
36 |   }
37 | }
38 | 


--------------------------------------------------------------------------------
/packages/serial/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'serial.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/soundfonts/README.md:
--------------------------------------------------------------------------------
1 | # @strudel/soundfonts
2 | 


--------------------------------------------------------------------------------
/packages/soundfonts/convert.js:
--------------------------------------------------------------------------------
 1 | // this script converts a soundfont into a json file, it has not been not used yet
 2 | import fetch from 'node-fetch';
 3 | 
 4 | const name = '0000_JCLive';
 5 | 
 6 | const js = await fetch(`https://felixroos.github.io/webaudiofontdata/sound/${name}_sf2_file.js`).then((res) =>
 7 |   res.text(),
 8 | );
 9 | // console.log(js);
10 | 
11 | let [_, data] = js.split('_sf2_file=');
12 | data = eval(data);
13 | console.log(JSON.stringify(data));
14 | 


--------------------------------------------------------------------------------
/packages/soundfonts/index.mjs:
--------------------------------------------------------------------------------
1 | import { getFontBufferSource, registerSoundfonts, setSoundfontUrl } from './fontloader.mjs';
2 | import * as soundfontList from './list.mjs';
3 | import { startPresetNote } from 'sfumato';
4 | import { loadSoundfont } from './sfumato.mjs';
5 | 
6 | export { loadSoundfont, startPresetNote, getFontBufferSource, soundfontList, registerSoundfonts, setSoundfontUrl };
7 | 


--------------------------------------------------------------------------------
/packages/soundfonts/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/soundfonts",
 3 |   "version": "1.2.3",
 4 |   "description": "Soundsfont support for strudel",
 5 |   "main": "index.mjs",
 6 |   "publishConfig": {
 7 |     "main": "dist/index.mjs"
 8 |   },
 9 |   "scripts": {
10 |     "build": "vite build",
11 |     "prepublishOnly": "npm run build"
12 |   },
13 |   "type": "module",
14 |   "repository": {
15 |     "type": "git",
16 |     "url": "git+https://github.com/tidalcycles/strudel.git"
17 |   },
18 |   "keywords": [
19 |     "tidalcycles",
20 |     "strudel",
21 |     "pattern",
22 |     "livecoding",
23 |     "algorave"
24 |   ],
25 |   "author": "Felix Roos ",
26 |   "license": "AGPL-3.0-or-later",
27 |   "bugs": {
28 |     "url": "https://github.com/tidalcycles/strudel/issues"
29 |   },
30 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
31 |   "dependencies": {
32 |     "@strudel/core": "workspace:*",
33 |     "@strudel/webaudio": "workspace:*",
34 |     "sfumato": "^0.1.2",
35 |     "soundfont2": "^0.5.0"
36 |   },
37 |   "devDependencies": {
38 |     "node-fetch": "^3.3.2",
39 |     "vite": "^6.0.11"
40 |   }
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/soundfonts/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/superdough/feedbackdelay.mjs:
--------------------------------------------------------------------------------
 1 | if (typeof DelayNode !== 'undefined') {
 2 |   class FeedbackDelayNode extends DelayNode {
 3 |     constructor(ac, wet, time, feedback) {
 4 |       super(ac);
 5 |       wet = Math.abs(wet);
 6 |       this.delayTime.value = time;
 7 | 
 8 |       const feedbackGain = ac.createGain();
 9 |       feedbackGain.gain.value = Math.min(Math.abs(feedback), 0.995);
10 |       this.feedback = feedbackGain.gain;
11 | 
12 |       const delayGain = ac.createGain();
13 |       delayGain.gain.value = wet;
14 |       this.delayGain = delayGain;
15 | 
16 |       this.connect(feedbackGain);
17 |       this.connect(delayGain);
18 |       feedbackGain.connect(this);
19 | 
20 |       this.connect = (target) => delayGain.connect(target);
21 |       return this;
22 |     }
23 |     start(t) {
24 |       this.delayGain.gain.setValueAtTime(this.delayGain.gain.value, t + this.delayTime.value);
25 |     }
26 |   }
27 | 
28 |   AudioContext.prototype.createFeedbackDelay = function (wet, time, feedback) {
29 |     return new FeedbackDelayNode(this, wet, time, feedback);
30 |   };
31 | }
32 | 


--------------------------------------------------------------------------------
/packages/superdough/index.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 | index.mjs - 
 3 | Copyright (C) 2022 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | export * from './superdough.mjs';
 8 | export * from './sampler.mjs';
 9 | export * from './helpers.mjs';
10 | export * from './synth.mjs';
11 | export * from './zzfx.mjs';
12 | export * from './logger.mjs';
13 | export * from './dspworklet.mjs';
14 | 


--------------------------------------------------------------------------------
/packages/superdough/logger.mjs:
--------------------------------------------------------------------------------
1 | let log = (msg) => console.log(msg);
2 | 
3 | export const logger = (...args) => log(...args);
4 | 
5 | export const setLogger = (fn) => {
6 |   log = fn;
7 | };
8 | 


--------------------------------------------------------------------------------
/packages/superdough/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "superdough",
 3 |   "version": "1.2.3",
 4 |   "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "directories": {
11 |     "example": "examples"
12 |   },
13 |   "scripts": {
14 |     "build": "vite build",
15 |     "prepublishOnly": "npm run build"
16 |   },
17 |   "repository": {
18 |     "type": "git",
19 |     "url": "git+https://github.com/tidalcycles/strudel.git"
20 |   },
21 |   "keywords": [
22 |     "tidalcycles",
23 |     "strudel",
24 |     "pattern",
25 |     "livecoding",
26 |     "algorave"
27 |   ],
28 |   "author": "Felix Roos ",
29 |   "license": "AGPL-3.0-or-later",
30 |   "bugs": {
31 |     "url": "https://github.com/tidalcycles/strudel/issues"
32 |   },
33 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
34 |   "devDependencies": {
35 |     "vite": "^6.0.11",
36 |     "vite-plugin-bundle-audioworklet": "workspace:*"
37 |   },
38 |   "dependencies": {
39 |     "nanostores": "^0.11.3"
40 |   }
41 | }
42 | 


--------------------------------------------------------------------------------
/packages/superdough/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
 5 | 
 6 | // https://vitejs.dev/config/
 7 | export default defineConfig({
 8 |   plugins: [bundleAudioWorkletPlugin()],
 9 |   build: {
10 |     lib: {
11 |       entry: resolve(__dirname, 'index.mjs'),
12 |       formats: ['es'],
13 |       fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.cjs' })[ext],
14 |     },
15 |     rollupOptions: {
16 |       external: [...Object.keys(dependencies)],
17 |     },
18 |     target: 'esnext',
19 |   },
20 | });
21 | 


--------------------------------------------------------------------------------
/packages/tidal/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/tidal",
 3 |   "version": "0.2.0",
 4 |   "private": true,
 5 |   "description": "Experimental Tidal Code interpreter",
 6 |   "module": "tidal.mjs",
 7 |   "repository": {
 8 |     "type": "git",
 9 |     "url": "git+https://github.com/tidalcycles/strudel/tree/main/packages/tidal"
10 |   },
11 |   "keywords": [
12 |     "haskell",
13 |     "javascript"
14 |   ],
15 |   "author": "Felix Roos ",
16 |   "license": "AGPL-3.0-or-later",
17 |   "bugs": {
18 |     "url": "https://github.com/tidalcycles/strudel/issues"
19 |   },
20 |   "homepage": "https://github.com/tidalcycles/strudel/tree/main/packages/hs2js",
21 |   "dependencies": {
22 |     "@strudel/core": "workspace:*",
23 |     "@strudel/mini": "workspace:*",
24 |     "hs2js": "workspace:*"
25 |   },
26 |   "devDependencies": {
27 |     "vite": "^6.0.11"
28 |   }
29 | }
30 | 


--------------------------------------------------------------------------------
/packages/tonal/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/tonal
 2 | 
 3 | This package adds tonal / harmonic functions to strudel Patterns.
 4 | 
 5 | ## Install
 6 | 
 7 | ```sh
 8 | npm i @strudel/tonal --save
 9 | ```
10 | 
11 | ## Example
12 | 
13 | ```js
14 | import { sequence } from '@strudel/core';
15 | import '@strudel/tonal';
16 | 
17 | const pattern = sequence(0, [1, 2]).scale('C major');
18 | 
19 | const events = pattern.firstCycle().map((e) => e.show());
20 | ```
21 | 
22 | yields:
23 | 
24 | ```js
25 | (0/1 -> 1/2, 0/1 -> 1/2, C3)
26 | (1/2 -> 3/4, 1/2 -> 3/4, D3)
27 | (3/4 -> 1/1, 3/4 -> 1/1, E3)
28 | ```
29 | 
30 | [play with @strudel/tonal codesandbox](https://codesandbox.io/s/strudel-tonal-example-rgc5if?file=/src/index.js)
31 | 
32 | ## Tonal API
33 | 
34 | See "Tonal API" in the [Strudel Tutorial](https://strudel.cc/learn/tonal)
35 | 


--------------------------------------------------------------------------------
/packages/tonal/index.mjs:
--------------------------------------------------------------------------------
 1 | import './tonal.mjs';
 2 | import './voicings.mjs';
 3 | 
 4 | export * from './tonal.mjs';
 5 | export * from './voicings.mjs';
 6 | 
 7 | import './ireal.mjs';
 8 | 
 9 | export const packageName = '@strudel/tonal';
10 | 


--------------------------------------------------------------------------------
/packages/tonal/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/tonal",
 3 |   "version": "1.2.2",
 4 |   "description": "Tonal functions for strudel",
 5 |   "main": "index.mjs",
 6 |   "publishConfig": {
 7 |     "main": "dist/index.mjs"
 8 |   },
 9 |   "scripts": {
10 |     "build": "vite build",
11 |     "test": "vitest run",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "type": "module",
15 |   "repository": {
16 |     "type": "git",
17 |     "url": "git+https://github.com/tidalcycles/strudel.git"
18 |   },
19 |   "keywords": [
20 |     "tidalcycles",
21 |     "strudel",
22 |     "pattern",
23 |     "livecoding",
24 |     "algorave"
25 |   ],
26 |   "author": "Felix Roos ",
27 |   "license": "AGPL-3.0-or-later",
28 |   "bugs": {
29 |     "url": "https://github.com/tidalcycles/strudel/issues"
30 |   },
31 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
32 |   "dependencies": {
33 |     "@strudel/core": "workspace:*",
34 |     "@tonaljs/tonal": "^4.10.0",
35 |     "chord-voicings": "^0.0.1",
36 |     "webmidi": "^3.1.12"
37 |   },
38 |   "devDependencies": {
39 |     "vite": "^6.0.11",
40 |     "vitest": "^3.0.4"
41 |   }
42 | }
43 | 


--------------------------------------------------------------------------------
/packages/tonal/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/transpiler/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/transpiler
 2 | 
 3 | This package contains a JS code transpiler with the following features:
 4 | 
 5 | - add locations of mini notation strings (double quoted or backticked) for highlighting
 6 | - converts pseudo note variables to note strings
 7 | - adds return statement to the last expression
 8 | 
 9 | ## Install
10 | 
11 | ```sh
12 | npm i @strudel/transpiler
13 | ```
14 | 
15 | ## Use
16 | 
17 | ```js
18 | import { transpiler } from '@strudel/core';
19 | import { evaluate } from '@strudel/core';
20 | 
21 | transpiler('note("c3 [e3,g3]")', { wrapAsync: false, addReturn: false, simpleLocs: true });
22 | /* mini('c3 [e3,g3]').withMiniLocation(7,17) */
23 | 
24 | evaluate(note('c3 [e3,g3]'), transpiler); // returns pattern of above code
25 | ```
26 | 


--------------------------------------------------------------------------------
/packages/transpiler/index.mjs:
--------------------------------------------------------------------------------
1 | import { evaluate as _evaluate } from '@strudel/core';
2 | import { transpiler } from './transpiler.mjs';
3 | export * from './transpiler.mjs';
4 | 
5 | export const evaluate = (code) => _evaluate(code, transpiler);
6 | 


--------------------------------------------------------------------------------
/packages/transpiler/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/transpiler",
 3 |   "version": "1.2.2",
 4 |   "description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "test": "vitest run",
13 |     "prepublishOnly": "npm run build"
14 |   },
15 |   "repository": {
16 |     "type": "git",
17 |     "url": "git+https://github.com/tidalcycles/strudel.git"
18 |   },
19 |   "keywords": [
20 |     "tidalcycles",
21 |     "strudel",
22 |     "pattern",
23 |     "livecoding",
24 |     "algorave"
25 |   ],
26 |   "author": "Felix Roos ",
27 |   "license": "AGPL-3.0-or-later",
28 |   "bugs": {
29 |     "url": "https://github.com/tidalcycles/strudel/issues"
30 |   },
31 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
32 |   "dependencies": {
33 |     "@strudel/core": "workspace:*",
34 |     "@strudel/mini": "workspace:*",
35 |     "acorn": "^8.14.0",
36 |     "escodegen": "^2.1.0",
37 |     "estree-walker": "^3.0.3"
38 |   },
39 |   "devDependencies": {
40 |     "vite": "^6.0.11",
41 |     "vitest": "^3.0.4"
42 |   }
43 | }
44 | 


--------------------------------------------------------------------------------
/packages/transpiler/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/vite-plugin-bundle-audioworklet/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "vite-plugin-bundle-audioworklet",
 3 |   "main": "./vite-plugin-bundle-audioworklet.js",
 4 |   "version": "0.1.1",
 5 |   "description": "",
 6 |   "keywords": [
 7 |     "vite",
 8 |     "audioworklet"
 9 |   ],
10 |   "author": "Felix Roos ",
11 |   "license": "MIT",
12 |   "type": "module",
13 |   "devDependencies": {
14 |     "vite": "^6.0.11"
15 |   }
16 | }
17 | 


--------------------------------------------------------------------------------
/packages/vite-plugin-bundle-audioworklet/vite-plugin-bundle-audioworklet.js:
--------------------------------------------------------------------------------
 1 | import { createLogger, build } from 'vite';
 2 | 
 3 | const end = '?audioworklet';
 4 | 
 5 | function bundleAudioWorkletPlugin() /* : PluginOption */ {
 6 |   let viteConfig /* : UserConfig */;
 7 | 
 8 |   return {
 9 |     name: 'vite-plugin-bundle-audioworklet',
10 |     /* apply: 'build', */
11 |     enforce: 'post',
12 | 
13 |     config(config) {
14 |       viteConfig = config;
15 |     },
16 | 
17 |     async transform(_code, id) {
18 |       if (!id.endsWith(end)) {
19 |         return;
20 |       }
21 |       const entry = id.replace(end, '');
22 |       const quietLogger = createLogger();
23 |       quietLogger.info = () => undefined;
24 | 
25 |       const output = await build({
26 |         configFile: false,
27 |         clearScreen: false,
28 |         customLogger: quietLogger,
29 |         build: {
30 |           lib: {
31 |             entry,
32 |             name: '_',
33 |             formats: ['iife'],
34 |           },
35 |           write: false,
36 |         },
37 |       });
38 |       if (!(output instanceof Array)) {
39 |         throw new Error('Expected output to be Array');
40 |       }
41 |       const iife = output[0].output[0].code;
42 |       const encoded = Buffer.from(iife, 'utf8').toString('base64');
43 |       return `export default "data:text/javascript;base64,${encoded}";`;
44 |     },
45 |   };
46 | }
47 | 
48 | export default bundleAudioWorkletPlugin;
49 | 


--------------------------------------------------------------------------------
/packages/web/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/web",
 3 |   "version": "1.2.3",
 4 |   "description": "Easy to setup, opiniated bundle of Strudel for the browser.",
 5 |   "module": "web.mjs",
 6 |   "publishConfig": {
 7 |     "main": "dist/index.js",
 8 |     "module": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "prepublishOnly": "npm run build"
13 |   },
14 |   "type": "module",
15 |   "repository": {
16 |     "type": "git",
17 |     "url": "git+https://github.com/tidalcycles/strudel.git"
18 |   },
19 |   "keywords": [
20 |     "tidalcycles",
21 |     "strudel",
22 |     "pattern",
23 |     "livecoding",
24 |     "algorave"
25 |   ],
26 |   "author": "Felix Roos ",
27 |   "contributors": [
28 |     "Alex McLean "
29 |   ],
30 |   "license": "AGPL-3.0-or-later",
31 |   "bugs": {
32 |     "url": "https://github.com/tidalcycles/strudel/issues"
33 |   },
34 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
35 |   "dependencies": {
36 |     "@strudel/core": "workspace:*",
37 |     "@strudel/mini": "workspace:*",
38 |     "@strudel/tonal": "workspace:*",
39 |     "@strudel/transpiler": "workspace:*",
40 |     "@strudel/webaudio": "workspace:*"
41 |   },
42 |   "devDependencies": {
43 |     "@rollup/plugin-replace": "^6.0.2",
44 |     "vite": "^6.0.11",
45 |     "vite-plugin-bundle-audioworklet": "workspace:*"
46 |   }
47 | }
48 | 


--------------------------------------------------------------------------------
/packages/web/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | import replace from '@rollup/plugin-replace';
 5 | import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
 6 | 
 7 | // https://vitejs.dev/config/
 8 | export default defineConfig({
 9 |   base: './',
10 |   plugins: [bundleAudioWorkletPlugin()],
11 |   build: {
12 |     lib: {
13 |       entry: resolve(__dirname, 'web.mjs'),
14 |       name: 'strudel',
15 |       formats: ['es', 'iife'],
16 |       fileName: (ext) => ({ es: 'index.mjs', iife: 'index.js' })[ext],
17 |     },
18 |     rollupOptions: {
19 |       // external: [...Object.keys(dependencies)],
20 |       plugins: [
21 |         replace({
22 |           'process.env.NODE_ENV': JSON.stringify('production'),
23 |           preventAssignment: true,
24 |         }),
25 |       ],
26 |     },
27 |     target: 'esnext',
28 |   },
29 | });
30 | 


--------------------------------------------------------------------------------
/packages/webaudio/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/webaudio
 2 | 
 3 | This package contains helpers to make music with strudel and the Web Audio API.
 4 | It is a thin binding to [superdough](https://www.npmjs.com/package/superdough).
 5 | 
 6 | ## Install
 7 | 
 8 | ```sh
 9 | npm i @strudel/webaudio --save
10 | ```
11 | 
12 | ## Example
13 | 
14 | ```js
15 | import { repl, note } from "@strudel/core";
16 | import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel/webaudio";
17 | 
18 | initAudioOnFirstClick();
19 | const ctx = getAudioContext();
20 | 
21 | const { scheduler } = repl({
22 |   defaultOutput: webaudioOutput,
23 |   getTime: () => ctx.currentTime
24 | });
25 | 
26 | const pattern = note("c3", ["eb3", "g3"]).s("sawtooth");
27 | 
28 | scheduler.setPattern(pattern);
29 | document.getElementById("start").addEventListener("click", () => scheduler.start());
30 | document.getElementById("stop").addEventListener("click", () => scheduler.stop());
31 | ```
32 | 
33 | [Play with the example codesandbox](https://codesandbox.io/s/amazing-dawn-gclfwg?file=/src/index.js).
34 | 
35 | Read more in the docs about [samples](https://strudel.cc/learn/samples/), [synths](https://strudel.cc/learn/synths/) and [effects](https://strudel.cc/learn/effects/).
36 | 


--------------------------------------------------------------------------------
/packages/webaudio/index.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 | index.mjs - 
 3 | Copyright (C) 2022 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | export * from './webaudio.mjs';
 8 | export * from './scope.mjs';
 9 | export * from './spectrum.mjs';
10 | export * from 'superdough';
11 | 


--------------------------------------------------------------------------------
/packages/webaudio/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/webaudio",
 3 |   "version": "1.2.3",
 4 |   "description": "Web Audio helpers for Strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "directories": {
 8 |     "example": "examples"
 9 |   },
10 |   "publishConfig": {
11 |     "main": "dist/index.mjs"
12 |   },
13 |   "scripts": {
14 |     "example": "npx parcel examples/repl.html",
15 |     "build": "vite build",
16 |     "prepublishOnly": "npm run build"
17 |   },
18 |   "repository": {
19 |     "type": "git",
20 |     "url": "git+https://github.com/tidalcycles/strudel.git"
21 |   },
22 |   "keywords": [
23 |     "tidalcycles",
24 |     "strudel",
25 |     "pattern",
26 |     "livecoding",
27 |     "algorave"
28 |   ],
29 |   "author": "Felix Roos ",
30 |   "license": "AGPL-3.0-or-later",
31 |   "bugs": {
32 |     "url": "https://github.com/tidalcycles/strudel/issues"
33 |   },
34 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
35 |   "dependencies": {
36 |     "@strudel/core": "workspace:*",
37 |     "@strudel/draw": "workspace:*",
38 |     "superdough": "workspace:*"
39 |   },
40 |   "devDependencies": {
41 |     "vite": "^6.0.11"
42 |   }
43 | }
44 | 


--------------------------------------------------------------------------------
/packages/webaudio/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/packages/xen/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/xen
 2 | 
 3 | This package adds xenharmonic / microtonal functions to strudel Patterns. Further documentation + examples will follow.
 4 | 
 5 | ## Install
 6 | 
 7 | ```sh
 8 | npm i @strudel/xen --save
 9 | ```
10 | 


--------------------------------------------------------------------------------
/packages/xen/index.mjs:
--------------------------------------------------------------------------------
1 | import './xen.mjs';
2 | import './tune.mjs';
3 | 
4 | export * from './xen.mjs';
5 | 


--------------------------------------------------------------------------------
/packages/xen/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "@strudel/xen",
 3 |   "version": "1.2.2",
 4 |   "description": "Xenharmonic API for strudel",
 5 |   "main": "index.mjs",
 6 |   "type": "module",
 7 |   "publishConfig": {
 8 |     "main": "dist/index.mjs"
 9 |   },
10 |   "scripts": {
11 |     "build": "vite build",
12 |     "test": "vitest run",
13 |     "prepublishOnly": "npm run build"
14 |   },
15 |   "repository": {
16 |     "type": "git",
17 |     "url": "git+https://github.com/tidalcycles/strudel.git"
18 |   },
19 |   "keywords": [
20 |     "tidalcycles",
21 |     "strudel",
22 |     "pattern",
23 |     "livecoding",
24 |     "algorave"
25 |   ],
26 |   "author": "Felix Roos ",
27 |   "license": "AGPL-3.0-or-later",
28 |   "bugs": {
29 |     "url": "https://github.com/tidalcycles/strudel/issues"
30 |   },
31 |   "homepage": "https://github.com/tidalcycles/strudel#readme",
32 |   "dependencies": {
33 |     "@strudel/core": "workspace:*"
34 |   },
35 |   "devDependencies": {
36 |     "vite": "^6.0.11",
37 |     "vitest": "^3.0.4"
38 |   }
39 | }
40 | 


--------------------------------------------------------------------------------
/packages/xen/test/xen.test.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 | xen.test.mjs - 
 3 | Copyright (C) 2022 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | import { edo } from '../xen.mjs';
 8 | import { describe, it, expect } from 'vitest';
 9 | 
10 | describe('xen', () => {
11 |   it('edo', () => {
12 |     expect(edo('3edo')).toEqual([1, Math.pow(2, 1 / 3), Math.pow(2, 2 / 3)]);
13 |   });
14 | });
15 | 


--------------------------------------------------------------------------------
/packages/xen/tune.mjs:
--------------------------------------------------------------------------------
 1 | /*
 2 | tune.mjs - 
 3 | Copyright (C) 2022 Strudel contributors - see 
 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
 5 | */
 6 | 
 7 | import Tune from './tunejs.js';
 8 | import { register } from '@strudel/core';
 9 | 
10 | export const tune = register('tune', (scale, pat) => {
11 |   const tune = new Tune();
12 |   if (!tune.isValidScale(scale)) {
13 |     throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
14 |   }
15 |   tune.loadScale(scale);
16 |   tune.tonicize(1);
17 |   return pat.withHap((hap) => {
18 |     return hap.withValue(() => tune.note(hap.value));
19 |   });
20 | });
21 | 


--------------------------------------------------------------------------------
/packages/xen/vite.config.js:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vite';
 2 | import { dependencies } from './package.json';
 3 | import { resolve } from 'path';
 4 | 
 5 | // https://vitejs.dev/config/
 6 | export default defineConfig({
 7 |   plugins: [],
 8 |   build: {
 9 |     lib: {
10 |       entry: resolve(__dirname, 'index.mjs'),
11 |       formats: ['es'],
12 |       fileName: (ext) => ({ es: 'index.mjs' })[ext],
13 |     },
14 |     rollupOptions: {
15 |       external: [...Object.keys(dependencies)],
16 |     },
17 |     target: 'esnext',
18 |   },
19 | });
20 | 


--------------------------------------------------------------------------------
/paper/Makefile:
--------------------------------------------------------------------------------
 1 | all: iclc2023.pdf iclc2023.html
 2 | 
 3 | clean:
 4 | 	rm iclc2023.pdf iclc2023.html
 5 | 
 6 | iclc2023.html: iclc2023.md citations.json
 7 | 	pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html
 8 | 
 9 | iclc2023.pdf: iclc2023.md citations.json pandoc/iclc.latex pandoc/iclc.sty
10 | 	pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf
11 | 
12 | iclc2023.docx: iclc2023.md citations.json
13 | 	pandoc --citeproc --number-sections iclc2023.md -o iclc2023.docx
14 | 
15 | iclc2023x.pdf: iclc2023.md citations.json pandoc/iclc.latex pandoc/iclc.sty
16 | 	pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md --pdf-engine=xelatex -o iclc2023x.pdf
17 | 


--------------------------------------------------------------------------------
/paper/README.md:
--------------------------------------------------------------------------------
 1 | # paper
 2 | 
 3 | ## iclc2023
 4 | 
 5 | from the strudel project root:
 6 | 
 7 | ```sh
 8 | npm run iclc
 9 | npm run iclc-nocite # try this if you get an error
10 | ```
11 | 
12 | ## old
13 | 
14 | Work in progress on a paper about strudel
15 | 
16 | To build you will need
17 | 
18 | - pandoc
19 |   - pandoc-url2cite (`npm install -g pandoc-url2cite`)
20 | - latex/xelatex
21 | - python
22 |   - pandocfilters (`pip3 install pandocfilters`)
23 | 


--------------------------------------------------------------------------------
/paper/bin/code-filter.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python3
 2 | import sys
 3 | 
 4 | from pandocfilters import toJSONFilter, RawBlock
 5 | 
 6 | def toMiniREPL(key, value, format, meta):
 7 |     # print(value, file=sys.stderr)
 8 |     if key == 'CodeBlock':
 9 |         return RawBlock("markdown", "")
10 | 
11 | if __name__ == "__main__":
12 |     toJSONFilter(toMiniREPL)
13 | 


--------------------------------------------------------------------------------
/paper/demo.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/demo.pdf


--------------------------------------------------------------------------------
/paper/iclc2023.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/iclc2023.pdf


--------------------------------------------------------------------------------
/paper/iclc2023x.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/iclc2023x.pdf


--------------------------------------------------------------------------------
/paper/images/cc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/images/cc.png


--------------------------------------------------------------------------------
/paper/images/strudel-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/images/strudel-screenshot.png


--------------------------------------------------------------------------------
/paper/images/strudel-screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/images/strudel-screenshot2.png


--------------------------------------------------------------------------------
/paper/images/strudelflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/images/strudelflow.png


--------------------------------------------------------------------------------
/paper/make.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | if [ -d "$HOME/.cabal/bin" ] ; then
 4 |     PATH="$HOME/.cabal/bin:$PATH"
 5 | fi
 6 | 
 7 | # --template=templates/template.latex \
 8 | 
 9 | pandoc -s demo.md \
10 |   --from markdown+auto_identifiers --pdf-engine=xelatex --template tex/latex-template.tex -V colorlinks --number-sections \
11 |   --citeproc --pdf-engine=xelatex \
12 |   --dpi=300 -o demo.pdf
13 | 
14 | pandoc -s demo.md --filter bin/code-filter.py \
15 |   --citeproc \
16 |   -t markdown-citations -t markdown-fenced_divs \
17 |   -o demo-preprocessed.md
18 | 


--------------------------------------------------------------------------------
/paper/paper.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/paper/paper.pdf


--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 |   # all packages in direct subdirs of packages/
3 |   - "packages/*"
4 |   - "examples/*"
5 |   - "tools/dbpatch"
6 |   - "website/"
7 | 


--------------------------------------------------------------------------------
/samples/README.md:
--------------------------------------------------------------------------------
1 | # samples folder
2 | 
3 | 1. copy any samples to this folder
4 | 2. run `npx @strudel/sampler` from this folder
5 | 3. add `samples('local:')` to your code
6 | 


--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 | 


--------------------------------------------------------------------------------
/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "app"
 3 | version = "0.1.0"
 4 | description = "A Tauri App"
 5 | authors = ["you"]
 6 | license = ""
 7 | repository = ""
 8 | default-run = "app"
 9 | edition = "2021"
10 | rust-version = "1.60"
11 | 
12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13 | 
14 | [build-dependencies]
15 | tauri-build = { version = "1.4.0", features = [] }
16 | 
17 | [dependencies]
18 | serde_json = "1.0"
19 | serde = { version = "1.0", features = ["derive"] }
20 | tauri = { version = "1.4.0", features = [ "dialog-all", "clipboard-write-text", "fs-all"] }
21 | midir = "0.9.1"
22 | tokio = { version = "1.29.0", features = ["full"] }
23 | rosc = "0.10.1"
24 | tauri-plugin-clipboard-manager = "2"
25 | 
26 | [features]
27 | # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
28 | # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
29 | # DO NOT REMOVE!!
30 | custom-protocol = ["tauri/custom-protocol"]
31 | 
32 | [profile.release]
33 | panic = "abort"
34 | codegen-units = 1
35 | lto = true
36 | opt-level = "s"
37 | 


--------------------------------------------------------------------------------
/src-tauri/README.md:
--------------------------------------------------------------------------------
 1 | # @strudel/tauri
 2 | 
 3 | Rust source files for building native desktop apps using Tauri
 4 | 
 5 | ## Usage
 6 | 
 7 | Install [Rust](https://rustup.rs/) on your system.
 8 | 
 9 | From the project root:
10 | 
11 | - install Strudel dependencies
12 | 
13 | ```js
14 | pnpm i
15 | ```
16 | 
17 | - to run Strudel for development
18 | 
19 | ```js
20 | pnpm tauri dev
21 | ```
22 | 
23 | - to build the binary and installer/bundle
24 | 
25 | ```js
26 | pnpm tauri build
27 | ```
28 | 
29 | The binary and installer can be found in the 'src-tauri/target/release/bundle' directory
30 | 


--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 |   tauri_build::build()
3 | }
4 | 


--------------------------------------------------------------------------------
/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/128x128.png


--------------------------------------------------------------------------------
/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/128x128@2x.png


--------------------------------------------------------------------------------
/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/32x32.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square107x107Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square142x142Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square150x150Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square284x284Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square30x30Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square310x310Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square44x44Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square71x71Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/Square89x89Logo.png


--------------------------------------------------------------------------------
/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/StoreLogo.png


--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/icon.icns


--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/icon.ico


--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/src-tauri/icons/icon.png


--------------------------------------------------------------------------------
/src-tauri/src/loggerbridge.rs:
--------------------------------------------------------------------------------
 1 | use std::sync::Arc;
 2 | use tauri::Window;
 3 | 
 4 | #[derive(Clone, serde::Serialize)]
 5 | pub struct LoggerPayload {
 6 |   pub message: String,
 7 |   pub message_type: String,
 8 | }
 9 | 
10 | #[derive(Clone)]
11 | pub struct Logger {
12 |   pub window: Arc,
13 | }
14 | 
15 | impl Logger {
16 |   pub fn log(&self, message: String, message_type: String) {
17 |     println!("{}", message);
18 |     let _ = self.window.emit("log-event", LoggerPayload { message, message_type });
19 |   }
20 | }
21 | 


--------------------------------------------------------------------------------
/test/examples.test.mjs:
--------------------------------------------------------------------------------
 1 | import { queryCode } from './runtime.mjs';
 2 | import { describe, it } from 'vitest';
 3 | import doc from '../doc.json';
 4 | 
 5 | const skippedExamples = [
 6 |   'absoluteOrientationGamma',
 7 |   'absoluteOrientationBeta',
 8 |   'absoluteOrientationAlpha',
 9 |   'orientationGamma',
10 |   'orientationBeta',
11 |   'orientationAlpha',
12 |   'rotationGamma',
13 |   'rotationBeta',
14 |   'rotationAlpha',
15 |   'gravityZ',
16 |   'gravityY',
17 |   'gravityX',
18 |   'accelerationZ',
19 |   'accelerationY',
20 |   'accelerationX',
21 |   'defaultmidimap',
22 |   'midimaps',
23 | ];
24 | 
25 | describe('runs examples', () => {
26 |   const { docs } = doc;
27 |   docs.forEach(async (doc) => {
28 |     if (skippedExamples.includes(doc.name)) {
29 |       return;
30 |     }
31 |     doc.examples?.forEach((example, i) => {
32 |       it(`example "${doc.name}" example index ${i}`, async ({ expect }) => {
33 |         const haps = await queryCode(example, 4);
34 |         expect(haps).toMatchSnapshot();
35 |       });
36 |     });
37 |   });
38 | });
39 | 


--------------------------------------------------------------------------------
/test/tunes.test.mjs:
--------------------------------------------------------------------------------
 1 | import { queryCode, testCycles } from './runtime.mjs';
 2 | import * as tunes from '../website/src/repl/tunes.mjs';
 3 | import { describe, it } from 'vitest';
 4 | 
 5 | const tuneKeys = Object.keys(tunes);
 6 | 
 7 | describe('renders tunes', () => {
 8 |   tuneKeys.forEach((key) => {
 9 |     it(`tune: ${key}`, async ({ expect }) => {
10 |       const haps = await queryCode(tunes[key], testCycles[key] || 1);
11 |       expect(haps).toMatchSnapshot();
12 |     });
13 |   });
14 | });
15 | 


--------------------------------------------------------------------------------
/tools/dbpatch/README.md:
--------------------------------------------------------------------------------
 1 | # dbpatch
 2 | 
 3 | this is a little script to update all patterns in the db. Go to supabase and export as csv as `code_rows.csv` to this folder.
 4 | Then run
 5 | 
 6 | ```sh
 7 | node dbpatch.mjs > code_rows_patched.csv
 8 | ```
 9 | 
10 | It will output a csv file with the changes implemented in dbpatch.mjs
11 | 


--------------------------------------------------------------------------------
/tools/dbpatch/dbpatch.mjs:
--------------------------------------------------------------------------------
 1 | import { parse } from 'csv-parse/sync';
 2 | import { readFileSync } from 'fs';
 3 | import { stringify } from 'csv-stringify/sync';
 4 | 
 5 | const hasCpsCall = (code) =>
 6 |   ['setcps', 'setCps', 'setCpm', 'setcpm'].reduce((acc, m) => acc || code.includes(`${m}`), false);
 7 | 
 8 | function withCps(code, cps) {
 9 |   if (hasCpsCall(code)) {
10 |     return code;
11 |   }
12 |   const lines = code.split('\n');
13 |   const firstNonLineComment = lines.findIndex((l) => !l.startsWith('//'));
14 |   const cpsCall = `setcps(${cps})`;
15 |   lines.splice(firstNonLineComment, 0, cpsCall);
16 |   return lines.join('\n');
17 | }
18 | 
19 | const dumpNew = readFileSync('./code_rows.csv', { encoding: 'utf-8' });
20 | 
21 | const records = parse(dumpNew, {
22 |   columns: true,
23 |   skip_empty_lines: true,
24 | });
25 | 
26 | const edited = records.map((entry) => ({
27 |   ...entry,
28 |   code: withCps(entry.code, 1),
29 | }));
30 | 
31 | console.log(stringify(edited));
32 | 


--------------------------------------------------------------------------------
/tools/dbpatch/package.json:
--------------------------------------------------------------------------------
1 | {
2 |   "dependencies": {
3 |     "csv": "^6.3.11"
4 |   }
5 | }
6 | 


--------------------------------------------------------------------------------
/vitest.config.mjs:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vitest/config';
 2 | import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet';
 3 | 
 4 | /// 
 5 | export default defineConfig({
 6 |   plugins: [bundleAudioWorkletPlugin()],
 7 |   test: {
 8 |     reporters: 'verbose',
 9 |     isolate: false,
10 |     silent: true,
11 |     exclude: [
12 |       '**/node_modules/**',
13 |       '**/dist/**',
14 |       '**/cypress/**',
15 |       '**/.{idea,git,cache,output,temp}/**',
16 |       '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress}.config.*',
17 |       '**/shared.test.mjs',
18 |     ],
19 |   },
20 | });
21 | 


--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
 1 | .astro
 2 | # build output
 3 | dist/
 4 | 
 5 | # dependencies
 6 | node_modules/
 7 | 
 8 | # logs
 9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 | 
14 | 
15 | # environment variables
16 | .env
17 | .env.production
18 | 
19 | # macOS-specific files
20 | .DS_Store
21 | 
22 | public/tree-sitter.wasm
23 | public/tree-sitter-haskell.wasm


--------------------------------------------------------------------------------
/website/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 |   "recommendations": ["astro-build.astro-vscode"],
3 |   "unwantedRecommendations": []
4 | }
5 | 


--------------------------------------------------------------------------------
/website/.vscode/launch.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "version": "0.2.0",
 3 |   "configurations": [
 4 |     {
 5 |       "command": "./node_modules/.bin/astro dev",
 6 |       "name": "Development server",
 7 |       "request": "launch",
 8 |       "type": "node-terminal"
 9 |     }
10 |   ]
11 | }
12 | 


--------------------------------------------------------------------------------
/website/agpl-header.txt:
--------------------------------------------------------------------------------
 1 | /* 
 2 | 
 3 | Strudel - javascript-based environment for live coding algorithmic (musical) patterns
 4 | https://strudel.cc / https://github.com/tidalcycles/strudel/
 5 | 
 6 | Copyright (C) Strudel contributors 
 7 | https://github.com/tidalcycles/strudel/graphs/contributors
 8 | 
 9 | This program is free software: you can redistribute it and/or modify
10 | it under the terms of the GNU Affero General Public License as published by
11 | the Free Software Foundation, either version 3 of the License, or
12 | (at your option) any later version.
13 | 
14 | This program is distributed in the hope that it will be useful,
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 | GNU Affero General Public License for more details.
18 | 
19 | You should have received a copy of the GNU Affero General Public License
20 | along with this program.  If not, see .
21 | 
22 | */
23 | 
24 | 


--------------------------------------------------------------------------------
/website/public/CNAME:
--------------------------------------------------------------------------------
1 | strudel.cc


--------------------------------------------------------------------------------
/website/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/favicon.ico


--------------------------------------------------------------------------------
/website/public/fonts/3270/3270-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/3270/3270-Regular.ttf


--------------------------------------------------------------------------------
/website/public/fonts/BigBlueTerminal/BigBlue_TerminalPlus.TTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/BigBlueTerminal/BigBlue_TerminalPlus.TTF


--------------------------------------------------------------------------------
/website/public/fonts/CutiePi/Cute_Aurora_demo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/CutiePi/Cute_Aurora_demo.ttf


--------------------------------------------------------------------------------
/website/public/fonts/CutiePi/LICENSE.txt:
--------------------------------------------------------------------------------
1 | 
2 | 100% free for personal and commercial use. 
3 | However it's limited on basic latin only, 
4 | contact riedjal@gmail.com for full glyph (based on ANSI encoding)
5 | and OTF features (alternates).
6 | 
7 | src: https://www.dafont.com/cute-aurora.font?text=%24%3A+s%28%22bd%285%2C8%29%22%29.superimpose%28x+%3D%3E+x.note%28%22c2%22%29.midi%28device%29%29


--------------------------------------------------------------------------------
/website/public/fonts/FiraCode/FiraCode-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/FiraCode/FiraCode-Regular.ttf


--------------------------------------------------------------------------------
/website/public/fonts/FiraCode/FiraCode-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/FiraCode/FiraCode-SemiBold.ttf


--------------------------------------------------------------------------------
/website/public/fonts/Hack/Hack-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/Hack/Hack-Regular.ttf


--------------------------------------------------------------------------------
/website/public/fonts/JetBrains/JetBrainsMono.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/JetBrains/JetBrainsMono.woff2


--------------------------------------------------------------------------------
/website/public/fonts/Monocraft/Monocraft.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/Monocraft/Monocraft.ttf


--------------------------------------------------------------------------------
/website/public/fonts/PressStart2P/PressStart2P-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/PressStart2P/PressStart2P-Regular.ttf


--------------------------------------------------------------------------------
/website/public/fonts/galactico/Galactico-Basic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/galactico/Galactico-Basic.otf


--------------------------------------------------------------------------------
/website/public/fonts/galactico/fontinfo.txt:
--------------------------------------------------------------------------------
1 | https://www.1001fonts.com/galactico-font.html
2 | 
3 | If the License is “Free” do what ever you want with it, even for commercial use, any “shout-out” or support thrown our way is much appreciated, even donating to buy a cup of coffee helps a lot. If the License is “For Personal Use Only” do what ever you want with it for yourself, but if you plan to make money out of it, meaning for commercial use, buy a license @ www.hellodonmarciano.com
4 | 
5 | I’ll appreciate your support when and if possible. Enjoy!
6 | 
7 | Galactico is licensed under the 1001Fonts Free For Commercial Use License (FFC)
8 | https://www.1001fonts.com/licenses/ffc.html


--------------------------------------------------------------------------------
/website/public/fonts/mode7/MODE7GX3.TTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/mode7/MODE7GX3.TTF


--------------------------------------------------------------------------------
/website/public/fonts/mode7/credit.txt:
--------------------------------------------------------------------------------
1 | This super cool font is by 3d@galax.xyz https://galax.xyz/TELETEXT/
2 | 


--------------------------------------------------------------------------------
/website/public/fonts/teletext/EuropeanTeletext.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/teletext/EuropeanTeletext.ttf


--------------------------------------------------------------------------------
/website/public/fonts/teletext/EuropeanTeletextNuevo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/teletext/EuropeanTeletextNuevo.ttf


--------------------------------------------------------------------------------
/website/public/fonts/we-come-in-peace/fontinfo.txt:
--------------------------------------------------------------------------------
1 | This font was created by Nate Piekos of Blambot Comic Fonts.  It's freeware to use as you'd like, but if you redistribute it, you must include this text file with the zip.
2 | 
3 | ~Nate Piekos
4 | 
5 | Blambot Comic Fonts
6 | http://www.piekosarts.com/blambotfonts
7 | blambot@piekosarts.com


--------------------------------------------------------------------------------
/website/public/fonts/we-come-in-peace/we-come-in-peace-bb.regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/fonts/we-come-in-peace/we-come-in-peace-bb.regular.ttf


--------------------------------------------------------------------------------
/website/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/icon.png


--------------------------------------------------------------------------------
/website/public/icons/apple-icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/icons/apple-icon-180.png


--------------------------------------------------------------------------------
/website/public/icons/manifest-icon-192.maskable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/icons/manifest-icon-192.maskable.png


--------------------------------------------------------------------------------
/website/public/icons/manifest-icon-512.maskable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/icons/manifest-icon-512.maskable.png


--------------------------------------------------------------------------------
/website/public/icons/strudel_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/icons/strudel_icon.png


--------------------------------------------------------------------------------
/website/public/img/autocomplete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/autocomplete.png


--------------------------------------------------------------------------------
/website/public/img/drumset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/drumset.png


--------------------------------------------------------------------------------
/website/public/img/strudel-alien-live-coding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/strudel-alien-live-coding.png


--------------------------------------------------------------------------------
/website/public/img/strudel-collaborative-coding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/strudel-collaborative-coding.png


--------------------------------------------------------------------------------
/website/public/img/strudel-live-coding-mars-college.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/strudel-live-coding-mars-college.jpg


--------------------------------------------------------------------------------
/website/public/img/strudel-monkeys.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/strudel-monkeys.png


--------------------------------------------------------------------------------
/website/public/img/strudel-scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/strudel-scope.png


--------------------------------------------------------------------------------
/website/public/img/strudel-themes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/strudel-themes.png


--------------------------------------------------------------------------------
/website/public/img/workshop-space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/img/workshop-space.png


--------------------------------------------------------------------------------
/website/public/make-scrollable-code-focusable.js:
--------------------------------------------------------------------------------
1 | Array.from(document.getElementsByTagName('pre')).forEach((element) => {
2 |   element.setAttribute('tabindex', '0');
3 | });
4 | 


--------------------------------------------------------------------------------
/website/public/manifest.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "short_name": "Strudel REPL",
 3 |   "name": "Strudel REPL - Tidal Patterns in JavaScript",
 4 |   "icons": [
 5 |     {
 6 |       "src": "favicon.ico",
 7 |       "sizes": "64x64 32x32 24x24 16x16",
 8 |       "type": "image/x-icon"
 9 |     }
10 |   ],
11 |   "start_url": ".",
12 |   "display": "standalone",
13 |   "theme_color": "#000000",
14 |   "background_color": "#ffffff"
15 | }
16 | 


--------------------------------------------------------------------------------
/website/public/piano.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "_base": "https://raw.githubusercontent.com/felixroos/dough-samples/main/piano/",
 3 |   "piano": {
 4 |     "A0": "A0v8.mp3",
 5 |     "C1": "C1v8.mp3",
 6 |     "Ds1": "Ds1v8.mp3",
 7 |     "Fs1": "Fs1v8.mp3",
 8 |     "A1": "A1v8.mp3",
 9 |     "C2": "C2v8.mp3",
10 |     "Ds2": "Ds2v8.mp3",
11 |     "Fs2": "Fs2v8.mp3",
12 |     "A2": "A2v8.mp3",
13 |     "C3": "C3v8.mp3",
14 |     "Ds3": "Ds3v8.mp3",
15 |     "Fs3": "Fs3v8.mp3",
16 |     "A3": "A3v8.mp3",
17 |     "C4": "C4v8.mp3",
18 |     "Ds4": "Ds4v8.mp3",
19 |     "Fs4": "Fs4v8.mp3",
20 |     "A4": "A4v8.mp3",
21 |     "C5": "C5v8.mp3",
22 |     "Fs5": "Fs5v8.mp3",
23 |     "A5": "A5v8.mp3",
24 |     "C6": "C6v8.mp3",
25 |     "Ds6": "Ds6v8.mp3",
26 |     "Fs6": "Fs6v8.mp3",
27 |     "A6": "A6v8.mp3",
28 |     "C7": "C7v8.mp3",
29 |     "Ds7": "Ds7v8.mp3",
30 |     "Fs7": "Fs7v8.mp3",
31 |     "A7": "A7v8.mp3",
32 |     "C8": "C8v8.mp3"
33 |   }
34 | }
35 | 


--------------------------------------------------------------------------------
/website/public/pwa/strudel-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/pwa/strudel-linux.png


--------------------------------------------------------------------------------
/website/public/pwa/strudel-macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/public/pwa/strudel-macos.png


--------------------------------------------------------------------------------
/website/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 | 


--------------------------------------------------------------------------------
/website/src/components/BlogPost.astro:
--------------------------------------------------------------------------------
 1 | ---
 2 | import type { CollectionEntry } from 'astro:content';
 3 | 
 4 | type Props = { post: CollectionEntry<'blog'> };
 5 | 
 6 | const { post } = Astro.props;
 7 | const { Content } = await post.render();
 8 | import { format } from 'date-fns';
 9 | ---
10 | 
11 | 
14 |
15 |
16 |

{post.data.title}

17 | 18 |
19 |

20 | 23 |

24 |
25 |
26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /website/src/components/BlogVideo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { src } = Astro.props; 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/components/Box.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import LightBulbIcon from '@heroicons/react/20/solid/LightBulbIcon'; 3 | //import MusicalNoteIcon from '@heroicons/react/20/solid/MusicalNoteIcon'; 4 | --- 5 | 6 |
7 |
8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /website/src/components/Claviature.jsx: -------------------------------------------------------------------------------- 1 | import { getClaviature } from 'claviature'; 2 | import React from 'react'; 3 | 4 | export default function Claviature({ options, onClick, onMouseDown, onMouseUp, onMouseLeave }) { 5 | const svg = getClaviature({ 6 | options, 7 | onClick, 8 | onMouseDown, 9 | onMouseUp, 10 | onMouseLeave, 11 | }); 12 | return ( 13 | 14 | {svg.children.map((el, i) => { 15 | const TagName = el.name; 16 | const { key, ...attributes } = el.attributes; 17 | return ( 18 | 19 | {el.value} 20 | 21 | ); 22 | })} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /website/src/components/HeadSEO.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { SITE, OPEN_GRAPH, type Frontmatter } from '../config'; 3 | 4 | export interface Props { 5 | frontmatter: Frontmatter; 6 | canonicalUrl: URL; 7 | } 8 | 9 | const { frontmatter, canonicalUrl } = Astro.props as Props; 10 | const formattedContentTitle = `${frontmatter.title} 🚀 ${SITE.title}`; 11 | const imageSrc = frontmatter.image?.src ?? OPEN_GRAPH.image.src; 12 | const canonicalImageSrc = new URL(imageSrc, Astro.site); 13 | const imageAlt = frontmatter.image?.alt ?? OPEN_GRAPH.image.alt; 14 | --- 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /website/src/components/Header/LanguageSelect.css: -------------------------------------------------------------------------------- 1 | .language-select { 2 | flex-grow: 1; 3 | width: 48px; 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0.33em 0.5em; 7 | overflow: visible; 8 | font-weight: 500; 9 | font-size: 1rem; 10 | font-family: inherit; 11 | line-height: inherit; 12 | background-color: var(--theme-bg); 13 | border-color: var(--theme-text-lighter); 14 | color: var(--theme-text-light); 15 | border-style: solid; 16 | border-width: 1px; 17 | border-radius: 0.25rem; 18 | outline: 0; 19 | cursor: pointer; 20 | transition-timing-function: ease-out; 21 | transition-duration: 0.2s; 22 | transition-property: border-color, color; 23 | -webkit-font-smoothing: antialiased; 24 | padding-left: 30px; 25 | padding-right: 1rem; 26 | } 27 | .language-select-wrapper .language-select:hover, 28 | .language-select-wrapper .language-select:focus { 29 | color: var(--theme-text); 30 | border-color: var(--theme-text-light); 31 | } 32 | .language-select-wrapper { 33 | color: var(--theme-text-light); 34 | position: relative; 35 | } 36 | .language-select-wrapper > svg { 37 | position: absolute; 38 | top: 7px; 39 | left: 10px; 40 | pointer-events: none; 41 | } 42 | 43 | @media (min-width: 50em) { 44 | .language-select { 45 | width: 100%; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /website/src/components/PageContent/PageContent.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import MoreMenu from '../RightSidebar/MoreMenu.astro'; 3 | 4 | type Props = { 5 | githubEditUrl?: string; 6 | }; 7 | 8 | const { githubEditUrl } = Astro.props as Props; 9 | --- 10 | 11 |
12 |
13 | 14 |
15 | 16 |
17 |
18 | 21 |
22 | 32 | -------------------------------------------------------------------------------- /website/src/components/QA.tsx: -------------------------------------------------------------------------------- 1 | import ChevronDownIcon from '@heroicons/react/20/solid/ChevronDownIcon'; 2 | import ChevronUpIcon from '@heroicons/react/20/solid/ChevronUpIcon'; 3 | import React from 'react'; 4 | import { useState } from 'react'; 5 | 6 | export default function QA({ children, q }) { 7 | const [visible, setVisible] = useState(false); 8 | return ( 9 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /website/src/components/RightSidebar/RightSidebar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import TableOfContents from './TableOfContents'; 3 | import MoreMenu from './MoreMenu.astro'; 4 | import type { MarkdownHeading } from 'astro'; 5 | import AvatarList from '../Footer/AvatarList.astro'; 6 | 7 | type Props = { 8 | headings: MarkdownHeading[]; 9 | githubEditUrl?: string; 10 | }; 11 | 12 | const { headings, githubEditUrl } = Astro.props as Props; 13 | let currentPage = Astro.url.pathname; 14 | // remove slash before # 15 | currentPage = currentPage.endsWith('/') ? currentPage.slice(0, -1) : currentPage; 16 | --- 17 | 18 | 23 | -------------------------------------------------------------------------------- /website/src/components/Udels/UdelFrame.jsx: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | export function UdelFrame({ onEvaluate, hash, instance }) { 4 | const ref = useRef(); 5 | window.addEventListener('message', (message) => { 6 | const childWindow = ref?.current?.contentWindow; 7 | if (message == null || message.source !== childWindow) { 8 | return; // Skip message in this event listener 9 | } 10 | onEvaluate(message.data); 11 | }); 12 | 13 | const url = new URL(window.location.origin); 14 | url.hash = hash; 15 | url.searchParams.append('instance', instance); 16 | const source = url.toString(); 17 | 18 | return ( 19 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /website/src/components/Udels/UdelsEditor.jsx: -------------------------------------------------------------------------------- 1 | import Loader from '@src/repl/components/Loader'; 2 | import { HorizontalPanel } from '@src/repl/components/panel/Panel'; 3 | import { Code } from '@src/repl/components/Code'; 4 | import BigPlayButton from '@src/repl/components/BigPlayButton'; 5 | import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; 6 | 7 | // type Props = { 8 | // context: replcontext, 9 | // } 10 | 11 | export default function UdelsEditor(Props) { 12 | const { context, ...editorProps } = Props; 13 | const { containerRef, editorRef, error, init, pending, started, handleTogglePlay } = context; 14 | 15 | return ( 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /website/src/components/Udels/UdelsHeader.jsx: -------------------------------------------------------------------------------- 1 | import NumberInput from '@src/repl/components/NumberInput'; 2 | 3 | export default function UdelsHeader(Props) { 4 | const { numWindows, setNumWindows } = Props; 5 | 6 | return ( 7 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/components/Youtube.jsx: -------------------------------------------------------------------------------- 1 | import LiteYouTubeEmbed from 'react-lite-youtube-embed'; 2 | import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'; 3 | 4 | export function Youtube(props) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /website/src/content/blog/release-0.4.0-brandung.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Release Notes v0.4.0' 3 | description: '' 4 | date: '2022-11-13' 5 | tags: ['meta'] 6 | author: froos 7 | --- 8 | 9 | ## What's Changed 10 | 11 | - new transpiler based on acorn by @felixroos in https://github.com/tidalcycles/strudel/pull/249 12 | - Webaudio build by @felixroos in https://github.com/tidalcycles/strudel/pull/250 13 | - Repl refactoring by @felixroos in https://github.com/tidalcycles/strudel/pull/255 14 | 15 | **Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.3.0...v0.4.0 16 | -------------------------------------------------------------------------------- /website/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, z } from 'astro:content'; 2 | 3 | const blog = defineCollection({ 4 | // Type-check frontmatter using a schema 5 | schema: z.object({ 6 | title: z.string(), 7 | author: z.string(), 8 | description: z.string().optional(), 9 | // Transform string to Date object 10 | date: z 11 | .string() 12 | .or(z.date()) 13 | .transform((val) => new Date(val)), 14 | updatedDate: z 15 | .string() 16 | .optional() 17 | .transform((str) => (str ? new Date(str) : undefined)), 18 | image: z.string().optional(), 19 | tags: z.array(z.string()).optional(), 20 | draft: z.boolean().optional(), 21 | }), 22 | }); 23 | 24 | export const collections = { blog }; 25 | -------------------------------------------------------------------------------- /website/src/cx.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | cx.js - 3 | Copyright (C) 2022 Strudel contributors - see 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . 5 | */ 6 | 7 | export default function cx(...classes) { 8 | // : Array 9 | return classes.filter(Boolean).join(' '); 10 | } 11 | -------------------------------------------------------------------------------- /website/src/docs/Colors.jsx: -------------------------------------------------------------------------------- 1 | import { colorMap } from '@strudel/draw'; 2 | 3 | const Colors = () => { 4 | return ( 5 |
6 | {Object.entries(colorMap).map(([name, hex]) => ( 7 |
8 |
9 |
{name}
10 |
11 |
{name}
12 |
13 |
14 | ))} 15 |
16 | ); 17 | }; 18 | 19 | export default Colors; 20 | -------------------------------------------------------------------------------- /website/src/docs/JsDoc.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { JsDoc } from './JsDoc'; 3 | const { name, h } = Astro.props; 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /website/src/docs/MiniRepl.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { MiniRepl } from './MiniRepl'; 3 | const { tune } = Astro.props; 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /website/src/docs/MiniRepl.css: -------------------------------------------------------------------------------- 1 | .mini-repl .cm-activeLine, 2 | .mini-repl .cm-activeLineGutter { 3 | background-color: transparent !important; 4 | } 5 | 6 | .mini-repl .cm-theme { 7 | background-color: var(--background); 8 | border: 1px solid var(--lineHighlight); 9 | padding: 2px; 10 | } 11 | 12 | .mini-repl .cm-scroller { 13 | font-family: inherit !important; 14 | } 15 | 16 | .mini-repl .cm-gutters { 17 | display: none !important; 18 | } 19 | 20 | .mini-repl .cm-cursorLayer { 21 | animation-name: inherit !important; 22 | } 23 | 24 | .mini-repl .cm-cursor { 25 | border-left: 2px solid currentcolor !important; 26 | } 27 | -------------------------------------------------------------------------------- /website/src/docs/docs.css: -------------------------------------------------------------------------------- 1 | .prose h1::before, 2 | .prose h2::before, 3 | .prose h3::before, 4 | .prose h4::before, 5 | .prose h5::before, 6 | .prose h6::before { 7 | display: block; 8 | content: ' '; 9 | margin-top: -70px; 10 | height: 70px; 11 | visibility: hidden; 12 | pointer-events: none; 13 | } 14 | 15 | .icon.icon-link { 16 | visibility: hidden; 17 | } 18 | 19 | .prose h1:hover .icon.icon-link, 20 | .prose h2:hover .icon.icon-link, 21 | .prose h3:hover .icon.icon-link, 22 | .prose h4:hover .icon.icon-link, 23 | .prose h5:hover .icon.icon-link, 24 | .prose h6:hover .icon.icon-link { 25 | visibility: visible; 26 | } 27 | 28 | .prose h1 > a, 29 | .prose h2 > a, 30 | .prose h3 > a, 31 | .prose h4 > a, 32 | .prose h5 > a, 33 | .prose h6 > a { 34 | text-decoration: none !important; 35 | } 36 | 37 | .icon.icon-link::after { 38 | content: '#'; 39 | margin-left: 8px; 40 | font-size: 0.9em; 41 | opacity: 50%; 42 | } 43 | -------------------------------------------------------------------------------- /website/src/docs/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module 'date-fns'; 5 | -------------------------------------------------------------------------------- /website/src/languages.ts: -------------------------------------------------------------------------------- 1 | import { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES } from './config'; 2 | export { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES }; 3 | 4 | export const langPathRegex = /\/([a-z]{2}-?[A-Z]{0,2})\//; 5 | 6 | export function getLanguageFromURL(pathname: string) { 7 | const langCodeMatch = pathname.match(langPathRegex); 8 | const langCode = langCodeMatch ? langCodeMatch[1] : 'en'; 9 | return langCode as (typeof KNOWN_LANGUAGE_CODES)[number]; 10 | } 11 | -------------------------------------------------------------------------------- /website/src/metadata_parser.js: -------------------------------------------------------------------------------- 1 | const ALLOW_MANY = ['by', 'url', 'genre', 'license']; 2 | 3 | export function getMetadata(raw_code) { 4 | if (raw_code == null) { 5 | console.error('could not extract metadata from missing pattern code'); 6 | raw_code = ''; 7 | } 8 | const comment_regexp = /\/\*([\s\S]*?)\*\/|\/\/(.*)$/gm; 9 | const comments = [...raw_code.matchAll(comment_regexp)].map((c) => (c[1] || c[2] || '').trim()); 10 | const tags = {}; 11 | 12 | const [prefix, title] = (comments[0] || '').split('"'); 13 | if (prefix.trim() === '' && title !== undefined) { 14 | tags['title'] = title; 15 | } 16 | 17 | for (const comment of comments) { 18 | const tag_matches = comment.split('@').slice(1); 19 | for (const tag_match of tag_matches) { 20 | let [tag, tag_value] = tag_match.split(/ (.*)/s); 21 | tag = tag.trim(); 22 | tag_value = (tag_value || '').replaceAll(/ +/g, ' ').trim(); 23 | 24 | if (ALLOW_MANY.includes(tag)) { 25 | const tag_list = tag_value 26 | .split(/[,\n]/) 27 | .map((t) => t.trim()) 28 | .filter((t) => t !== ''); 29 | tags[tag] = tag in tags ? tags[tag].concat(tag_list) : tag_list; 30 | } else { 31 | tag_value = tag_value.replaceAll(/\s+/g, ' '); 32 | tags[tag] = tag in tags ? tags[tag] + ' ' + tag_value : tag_value; 33 | } 34 | } 35 | } 36 | 37 | return tags; 38 | } 39 | -------------------------------------------------------------------------------- /website/src/my_patterns.js: -------------------------------------------------------------------------------- 1 | import { getMetadata } from './metadata_parser'; 2 | 3 | export function getMyPatterns() { 4 | const my = import.meta.glob('../../my-patterns/**', { as: 'raw', eager: true }); 5 | return Object.fromEntries( 6 | Object.entries(my) 7 | .filter(([name]) => name.endsWith('.txt')) 8 | .map(([name, raw]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]), 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /website/src/pages/de/workshop/index.astro: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /website/src/pages/embed.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import HeadCommon from '../components/HeadCommon.astro'; 3 | import { Repl } from '../repl/Repl.jsx'; 4 | --- 5 | 6 | 7 | 8 | 9 | Strudel REPL 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /website/src/pages/examples/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import * as tunes from '../../../src/repl/tunes.mjs'; 3 | import HeadCommon from '../../components/HeadCommon.astro'; 4 | 5 | import { getMetadata } from '../../metadata_parser'; 6 | 7 | const { BASE_URL } = import.meta.env; 8 | const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; 9 | --- 10 | 11 | 12 | 13 | 14 | 15 |
16 | { 17 | Object.entries(tunes).map(([name, tune]) => ( 18 | 22 |
23 | {getMetadata(tune)['title'] || name} 24 |
25 |
26 | )) 27 | } 28 |
29 | 30 | ../../metadata_parser 31 | -------------------------------------------------------------------------------- /website/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import HeadCommon from '../components/HeadCommon.astro'; 3 | import { Repl } from '../repl/Repl'; 4 | --- 5 | 6 | 7 | 8 | 9 | Strudel REPL 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/src/pages/intro/showcase.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Showcase 3 | layout: ../../layouts/MainLayout.astro 4 | --- 5 | 6 | import { Showcase } from '../../components/Showcase'; 7 | 8 | # Showcase 9 | 10 | This page contains a randomly shuffled selection of videos that show people using strudel in some way. 11 | 12 | 13 | -------------------------------------------------------------------------------- /website/src/pages/learn/accumulation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Accumulation Modifiers 3 | layout: ../../layouts/MainLayout.astro 4 | --- 5 | 6 | import { MiniRepl } from '../../docs/MiniRepl'; 7 | import { JsDoc } from '../../docs/JsDoc'; 8 | 9 | # Accumulation Modifiers 10 | 11 | ## superimpose 12 | 13 | 14 | 15 | ## layer 16 | 17 | 18 | 19 | ## off 20 | 21 | 22 | 23 | ## echo 24 | 25 | 26 | 27 | ## echoWith 28 | 29 | 30 | 31 | ## stut 32 | 33 | 34 | 35 | There are also [Tonal Functions](/learn/tonal). 36 | -------------------------------------------------------------------------------- /website/src/pages/learn/colors.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Colors 3 | layout: ../../layouts/MainLayout.astro 4 | --- 5 | 6 | import { MiniRepl } from '../../docs/MiniRepl'; 7 | import { JsDoc } from '../../docs/JsDoc'; 8 | import Colors from '../../docs/Colors.jsx'; 9 | 10 | # Colors 11 | 12 | 13 | -------------------------------------------------------------------------------- /website/src/pages/learn/devicemotion.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Device Motion 3 | layout: ../../layouts/MainLayout.astro 4 | --- 5 | 6 | import { MiniRepl } from '../../docs/MiniRepl'; 7 | import { JsDoc } from '../../docs/JsDoc'; 8 | import DeviceMotion from '@strudel/motion/docs/devicemotion.mdx'; 9 | 10 | 11 | -------------------------------------------------------------------------------- /website/src/pages/learn/index.astro: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /website/src/pages/learn/input-devices.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Input Devices 3 | layout: ../../layouts/MainLayout.astro 4 | --- 5 | 6 | import { MiniRepl } from '../../docs/MiniRepl'; 7 | import { JsDoc } from '../../docs/JsDoc'; 8 | 9 | import Gamepad from '@strudel/gamepad/docs/gamepad.mdx'; 10 | 11 | # Input Devices 12 | 13 | Strudel supports various input devices like Gamepads and MIDI controllers to manipulate patterns in real-time. 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/src/pages/learn/signals.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Signals 3 | layout: ../../layouts/MainLayout.astro 4 | --- 5 | 6 | import { MiniRepl } from '../../docs/MiniRepl'; 7 | import { JsDoc } from '../../docs/JsDoc'; 8 | 9 | # Continuous Signals 10 | 11 | Signals are patterns with continuous values, meaning they have theoretically infinite steps. 12 | They can provide streams of numbers that can be sampled at discrete points in time. 13 | 14 | ## saw 15 | 16 | 17 | 18 | ## sine 19 | 20 | 21 | 22 | ## cosine 23 | 24 | 25 | 26 | ## tri 27 | 28 | 29 | 30 | ## square 31 | 32 | 33 | 34 | ## rand 35 | 36 | 37 | 38 | ## Ranges from -1 to 1 39 | 40 | There is also `saw2`, `sine2`, `cosine2`, `tri2`, `square2` and `rand2` which have a range from -1 to 1! 41 | 42 | ## perlin 43 | 44 | 45 | 46 | ## irand 47 | 48 | 49 | 50 | ## brand 51 | 52 | 53 | 54 | ## brandBy 55 | 56 | 57 | 58 | ## mouseX 59 | 60 | 61 | 62 | ## mouseY 63 | 64 | 65 | 66 | Next up: [Random Modifiers](/learn/random-modifiers) 67 | -------------------------------------------------------------------------------- /website/src/pages/rss.xml.js: -------------------------------------------------------------------------------- 1 | import rss from '@astrojs/rss'; 2 | import { getCollection } from 'astro:content'; 3 | 4 | export async function GET(context) { 5 | const posts = (await getCollection('blog')).filter((p) => !p.data.draft); 6 | const options = { 7 | title: 'Strudel Blog', 8 | description: 9 | 'The Strudel Blog will keep you updated with the latest changes and things happening in the strudelsphere.', 10 | site: context.site, 11 | items: posts.map((post) => ({ 12 | link: `/blog/#${post.slug}`, 13 | title: post.data.title, 14 | pubDate: post.data.date, 15 | description: post.data.description, 16 | })), 17 | }; 18 | return rss(options); 19 | } 20 | -------------------------------------------------------------------------------- /website/src/pages/swatch/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getMyPatterns } from '../../my_patterns.js'; 3 | 4 | import { Content } from '../../../../my-patterns/README.md'; 5 | import HeadCommon from '../../components/HeadCommon.astro'; 6 | 7 | const myPatterns = await getMyPatterns(); 8 | 9 | const { BASE_URL } = import.meta.env; 10 | const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; 11 | --- 12 | 13 | 14 | 15 | 16 | 17 | { 18 | Object.keys(myPatterns).length === 0 && ( 19 |
20 | 21 |
22 | ) 23 | } 24 |
25 | { 26 | Object.entries(myPatterns).map(([name, tune]) => ( 27 | 31 |
32 | {name} 33 |
34 |
35 | )) 36 | } 37 |
38 | 39 | ../../list.json 40 | -------------------------------------------------------------------------------- /website/src/pages/technical-manual/about.mdx: -------------------------------------------------------------------------------- 1 | This section introduces you to Strudel in a technical sense. If you just want to _use_ Strudel, have a look at the [Tutorial](/workshop/getting-started). 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /website/src/pages/technical-manual/testing.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing 3 | layout: ../../layouts/MainLayout.astro 4 | --- 5 | 6 | # Testing 7 | 8 | Strudel uses [vitest](https://vitest.dev/) for testing, with 2 types of testing strategies: 9 | 10 | - unit tests for fine grained testing 11 | - automated snapshot tests for broader testing 12 | 13 | ## Unit Tests 14 | 15 | Each package has a `test` folder where tests are written on a file by file basis, e.g. `util.test.mjs` implements all tests for `util.mjs`. 16 | 17 | ## Snapshot Tests 18 | 19 | Snapshot tests allow testing larger chunks of data. Strudel uses snapshot tests for: 20 | 21 | - Example Snippets: `examples.test.mjs`, using snippets under `@example` inside jsdoc comments 22 | - Example Tunes: `tunes.test.mjs`, using all patterns in `tunes.mjs` 23 | 24 | The snapshot (`.snap`) files contain all haps within a certain number of cycles for each tested pattern. 25 | They allow testing for breaking changes on a larger scale. 26 | If breaking changes are intentional, the snapshots can be updated with `npm run snapshot`. 27 | Just make sure to verify that all affected patterns behave as expected. 28 | -------------------------------------------------------------------------------- /website/src/pages/tutorial/index.astro: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /website/src/pages/udels/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import HeadCommon from '@components/HeadCommon.astro'; 3 | import { Udels } from '../../components/Udels/Udels.jsx'; 4 | --- 5 | 6 | 7 | 8 | 9 | Strudel UDELS 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /website/src/pages/workshop/index.astro: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /website/src/pwa.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { registerSW } from 'virtual:pwa-register'; 3 | 4 | registerSW({ 5 | immediate: true, 6 | onRegisteredSW(swScriptUrl) { 7 | // console.log('SW registered: ', swScriptUrl) 8 | }, 9 | onOfflineReady() { 10 | // console.log('PWA application ready to work offline') 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /website/src/repl/Repl.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: #222; 3 | --lineBackground: #22222299; 4 | --foreground: #fff; 5 | --caret: #ffcc00; 6 | --selection: rgba(128, 203, 196, 0.5); 7 | --selectionMatch: #036dd626; 8 | --lineHighlight: #00000050; 9 | --gutterBackground: transparent; 10 | --gutterForeground: #8a919966; 11 | } 12 | 13 | .darken::before { 14 | content: ' '; 15 | position: fixed; 16 | top: 0; 17 | left: 0; 18 | width: 100vw; 19 | height: 100vh; 20 | display: block; 21 | background: black; 22 | opacity: 0.5; 23 | } 24 | 25 | #code .cm-scroller { 26 | padding-top: 10px !important; 27 | height: 100%; 28 | font-family: inherit; 29 | } 30 | #code .cm-content { 31 | padding-bottom: 50vh; 32 | } 33 | #code .cm-line > * { 34 | background: var(--lineBackground); 35 | } 36 | 37 | #code .cm-editor { 38 | background-color: transparent !important; 39 | height: 100%; 40 | z-index: 11; 41 | } 42 | 43 | #code .cm-theme { 44 | width: 100%; 45 | height: 100%; 46 | } 47 | 48 | #code .cm-theme-light { 49 | width: 100%; 50 | } 51 | 52 | #code .cm-cursorLayer { 53 | animation-name: inherit !important; 54 | } 55 | 56 | #code .cm-cursor { 57 | border-left: 2px solid currentcolor !important; 58 | } 59 | 60 | #code .cm-foldGutter { 61 | display: none !important; 62 | } 63 | 64 | #code .cm-focused { 65 | outline: none; 66 | } 67 | 68 | #code .cm-matchingBracket { 69 | text-decoration: underline 0.18rem; 70 | text-underline-offset: 0.22rem; 71 | } 72 | -------------------------------------------------------------------------------- /website/src/repl/Repl.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | Repl.jsx - 3 | Copyright (C) 2022 Strudel contributors - see 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . 5 | */ 6 | 7 | import { isIframe, isUdels } from './util.mjs'; 8 | import UdelsEditor from '@components/Udels/UdelsEditor'; 9 | import ReplEditor from './components/ReplEditor'; 10 | import EmbeddedReplEditor from './components/EmbeddedReplEditor'; 11 | import { useReplContext } from './useReplContext'; 12 | import { useSettings } from '@src/settings.mjs'; 13 | 14 | export function Repl({ embedded = false }) { 15 | const isEmbedded = embedded || isIframe(); 16 | const Editor = isUdels() ? UdelsEditor : isEmbedded ? EmbeddedReplEditor : ReplEditor; 17 | const context = useReplContext(); 18 | const { fontFamily } = useSettings(); 19 | return ; 20 | } 21 | -------------------------------------------------------------------------------- /website/src/repl/components/BigPlayButton.jsx: -------------------------------------------------------------------------------- 1 | import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; 2 | 3 | // type Props = { 4 | // started: boolean; 5 | // handleTogglePlay: () => void; 6 | // }; 7 | export default function BigPlayButton(Props) { 8 | const { started, handleTogglePlay } = Props; 9 | if (started) { 10 | return; 11 | } 12 | 13 | return ( 14 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /website/src/repl/components/Code.jsx: -------------------------------------------------------------------------------- 1 | // type Props = { 2 | // containerRef: React.MutableRefObject, 3 | // editorRef: React.MutableRefObject, 4 | // init: () => void 5 | // } 6 | export function Code(Props) { 7 | const { editorRef, containerRef, init } = Props; 8 | 9 | return ( 10 |
{ 14 | containerRef.current = el; 15 | if (!editorRef.current) { 16 | init(); 17 | } 18 | }} 19 | >
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /website/src/repl/components/EmbeddedReplEditor.jsx: -------------------------------------------------------------------------------- 1 | import Loader from '@src/repl/components/Loader'; 2 | import { Code } from '@src/repl/components/Code'; 3 | import BigPlayButton from '@src/repl/components/BigPlayButton'; 4 | import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; 5 | import { Header } from './Header'; 6 | 7 | // type Props = { 8 | // context: replcontext, 9 | // } 10 | 11 | export default function EmbeddedReplEditor(Props) { 12 | const { context, ...editorProps } = Props; 13 | const { pending, started, handleTogglePlay, containerRef, editorRef, error, init } = context; 14 | return ( 15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /website/src/repl/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import cx from '@src/cx.mjs'; 2 | 3 | function Loader({ active }) { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 | ); 11 | } 12 | export default Loader; 13 | -------------------------------------------------------------------------------- /website/src/repl/components/ReplEditor.jsx: -------------------------------------------------------------------------------- 1 | import Loader from '@src/repl/components/Loader'; 2 | import { HorizontalPanel, VerticalPanel } from '@src/repl/components/panel/Panel'; 3 | import { Code } from '@src/repl/components/Code'; 4 | import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; 5 | import { Header } from './Header'; 6 | import { useSettings } from '@src/settings.mjs'; 7 | 8 | // type Props = { 9 | // context: replcontext, 10 | // } 11 | 12 | export default function ReplEditor(Props) { 13 | const { context, ...editorProps } = Props; 14 | const { containerRef, editorRef, error, init, pending } = context; 15 | const settings = useSettings(); 16 | const { panelPosition, isZen } = settings; 17 | 18 | return ( 19 |
20 | 21 |
22 |
23 | 24 | {!isZen && panelPosition === 'right' && } 25 |
26 | 27 | {!isZen && panelPosition === 'bottom' && } 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /website/src/repl/components/UserFacingErrorMessage.jsx: -------------------------------------------------------------------------------- 1 | // type Props = { error: Error | null }; 2 | export default function UserFacingErrorMessage(Props) { 3 | const { error } = Props; 4 | if (error == null) { 5 | return; 6 | } 7 | return ( 8 |
9 | Error: {error.message || 'Unknown Error :-/'} 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /website/src/repl/components/pagination/Pagination.jsx: -------------------------------------------------------------------------------- 1 | import { Incrementor } from '../incrementor/Incrementor'; 2 | 3 | export function Pagination({ currPage, onPageChange, className, ...incrementorProps }) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /website/src/repl/components/panel/AudioDeviceSelector.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import { SelectInput } from './SelectInput'; 4 | import { getAudioDevices } from '@strudel/webaudio'; 5 | 6 | const initdevices = new Map(); 7 | 8 | // Allows the user to select an audio interface for Strudel to play through 9 | export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) { 10 | const [devices, setDevices] = useState(initdevices); 11 | const devicesInitialized = devices.size > 0; 12 | 13 | const onClick = () => { 14 | if (devicesInitialized) { 15 | return; 16 | } 17 | getAudioDevices().then((devices) => { 18 | setDevices(devices); 19 | }); 20 | }; 21 | const onDeviceChange = (deviceName) => { 22 | if (!devicesInitialized) { 23 | return; 24 | } 25 | onChange(deviceName); 26 | }; 27 | const options = new Map(); 28 | Array.from(devices.keys()).forEach((deviceName) => { 29 | options.set(deviceName, deviceName); 30 | }); 31 | return ( 32 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /website/src/repl/components/panel/AudioEngineTargetSelector.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { audioEngineTargets } from '../../../settings.mjs'; 3 | import { SelectInput } from './SelectInput'; 4 | 5 | // Allows the user to select an audio interface for Strudel to play through 6 | export function AudioEngineTargetSelector({ target, onChange, isDisabled }) { 7 | const onTargetChange = (target) => { 8 | onChange(target); 9 | }; 10 | const options = new Map([ 11 | [audioEngineTargets.webaudio, audioEngineTargets.webaudio], 12 | [audioEngineTargets.osc, audioEngineTargets.osc], 13 | ]); 14 | return ( 15 |
16 | 17 | {target === audioEngineTargets.osc && ( 18 |
19 |

20 | ⚠ All events routed to OSC, audio is silenced! See{' '} 21 | 22 | Docs 23 | 24 |

25 |
26 | )} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /website/src/repl/components/panel/Forms.jsx: -------------------------------------------------------------------------------- 1 | import cx from '@src/cx.mjs'; 2 | 3 | export function ButtonGroup({ value, onChange, items }) { 4 | return ( 5 |
6 | {Object.entries(items).map(([key, label], i, arr) => ( 7 | 21 | ))} 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /website/src/repl/components/panel/SelectInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // value: ?ID, options: Map, onChange: ID => null, onClick: event => void, isDisabled: boolean 3 | export function SelectInput({ value, options, onChange, onClick, isDisabled }) { 4 | return ( 5 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/repl/components/textbox/Textbox.jsx: -------------------------------------------------------------------------------- 1 | import cx from '@src/cx.mjs'; 2 | 3 | export function Textbox({ onChange, className, ...inputProps }) { 4 | return ( 5 | onChange(e.target.value)} 11 | {...inputProps} 12 | /> 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /website/src/repl/components/useLogger.jsx: -------------------------------------------------------------------------------- 1 | import useEvent from '@src/useEvent.mjs'; 2 | import { logger } from '@strudel/core'; 3 | import { nanoid } from 'nanoid'; 4 | import { atom } from 'nanostores'; 5 | 6 | export const $strudel_log_history = atom([]); 7 | 8 | function useLoggerEvent(onTrigger) { 9 | useEvent(logger.key, onTrigger); 10 | } 11 | 12 | function getUpdatedLog(log, event) { 13 | const { message, type, data } = event.detail; 14 | const lastLog = log.length ? log[log.length - 1] : undefined; 15 | const id = nanoid(12); 16 | if (type === 'loaded-sample') { 17 | const loadIndex = log.findIndex(({ data: { url }, type }) => type === 'load-sample' && url === data.url); 18 | log[loadIndex] = { message, type, id, data }; 19 | } else if (lastLog && lastLog.message === message) { 20 | log = log.slice(0, -1).concat([{ message, type, count: (lastLog.count ?? 1) + 1, id, data }]); 21 | } else { 22 | log = log.concat([{ message, type, id, data }]); 23 | } 24 | return log.slice(-20); 25 | } 26 | 27 | export function useLogger() { 28 | useLoggerEvent((event) => { 29 | const log = $strudel_log_history.get(); 30 | const newLog = getUpdatedLog(log, event); 31 | $strudel_log_history.set(newLog); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /website/src/repl/components/usedebounce.jsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { useEffect } from 'react'; 3 | import { useRef } from 'react'; 4 | 5 | function debounce(fn, wait) { 6 | let timer; 7 | return function (...args) { 8 | if (timer) { 9 | clearTimeout(timer); 10 | } 11 | timer = setTimeout(() => fn(...args), wait); 12 | }; 13 | } 14 | 15 | export function useDebounce(callback) { 16 | const ref = useRef; 17 | useEffect(() => { 18 | ref.current = callback; 19 | }, [callback]); 20 | 21 | const debouncedCallback = useMemo(() => { 22 | const func = () => { 23 | ref.current?.(); 24 | }; 25 | 26 | return debounce(func, 1000); 27 | }, []); 28 | 29 | return debouncedCallback; 30 | } 31 | -------------------------------------------------------------------------------- /website/src/repl/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidalcycles/strudel/b312ff63a9b847af9c74abe007ddfd29f6bfb016/website/src/repl/favicon.ico -------------------------------------------------------------------------------- /website/src/repl/piano.mjs: -------------------------------------------------------------------------------- 1 | import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; 2 | 3 | const maxPan = noteToMidi('C8'); 4 | const panwidth = (pan, width) => pan * width + (1 - width) / 2; 5 | 6 | Pattern.prototype.piano = function () { 7 | return this.clip(1) 8 | .s('piano') 9 | .release(0.1) 10 | .fmap((value) => { 11 | const midi = valueToMidi(value); 12 | // pan by pitch 13 | const pan = panwidth(Math.min(Math.round(midi) / maxPan, 1), 0.5); 14 | return { ...value, pan: (value.pan || 1) * pan }; 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /website/src/repl/useExamplePatterns.jsx: -------------------------------------------------------------------------------- 1 | import { $featuredPatterns, $publicPatterns, patternFilterName } from '../user_pattern_utils.mjs'; 2 | import { useStore } from '@nanostores/react'; 3 | import { useMemo } from 'react'; 4 | import * as tunes from '../repl/tunes.mjs'; 5 | 6 | export const stockPatterns = Object.fromEntries( 7 | Object.entries(tunes).map(([key, code], i) => [i, { id: i, code, collection: 'Stock Examples' }]), 8 | ); 9 | 10 | export const useExamplePatterns = () => { 11 | const featuredPatterns = useStore($featuredPatterns); 12 | const publicPatterns = useStore($publicPatterns); 13 | const collections = useMemo(() => { 14 | const pats = new Map(); 15 | pats.set(patternFilterName.featured, featuredPatterns); 16 | pats.set(patternFilterName.public, publicPatterns); 17 | // pats.set(patternFilterName.stock, stockPatterns); 18 | return pats; 19 | }, [featuredPatterns, publicPatterns]); 20 | 21 | const patterns = useMemo(() => { 22 | return Object.assign({}, ...collections.values()); 23 | }, [collections]); 24 | 25 | return { patterns, collections }; 26 | }; 27 | -------------------------------------------------------------------------------- /website/src/tauri.mjs: -------------------------------------------------------------------------------- 1 | import { invoke } from '@tauri-apps/api/core'; 2 | 3 | export const Invoke = invoke; 4 | export const isTauri = () => window.__TAURI_IPC__ != null; 5 | -------------------------------------------------------------------------------- /website/src/useClient.mjs: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export default function useClient() { 4 | const [client, setClient] = useState(false); 5 | useEffect(() => { 6 | setClient(true); 7 | }, []); 8 | return client; 9 | } 10 | -------------------------------------------------------------------------------- /website/src/useEvent.mjs: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | function useEvent(name, onTrigger, useCapture = false) { 4 | useEffect(() => { 5 | document.addEventListener(name, onTrigger, useCapture); 6 | return () => { 7 | document.removeEventListener(name, onTrigger, useCapture); 8 | }; 9 | }, [onTrigger]); 10 | } 11 | 12 | export default useEvent; 13 | -------------------------------------------------------------------------------- /website/src/useFrame.mjs: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | function useFrame(callback, autostart = false) { 4 | const requestRef = useRef(); 5 | const previousTimeRef = useRef(); 6 | 7 | const animate = (time) => { 8 | if (previousTimeRef.current !== undefined) { 9 | const deltaTime = time - previousTimeRef.current; 10 | callback(time, deltaTime); 11 | } 12 | previousTimeRef.current = time; 13 | requestRef.current = requestAnimationFrame(animate); 14 | }; 15 | 16 | const start = () => { 17 | requestRef.current = requestAnimationFrame(animate); 18 | }; 19 | const stop = () => { 20 | requestRef.current && cancelAnimationFrame(requestRef.current); 21 | delete requestRef.current; 22 | }; 23 | useEffect(() => { 24 | if (requestRef.current) { 25 | stop(); 26 | start(); 27 | } 28 | }, [callback]); 29 | 30 | useEffect(() => { 31 | if (autostart) { 32 | start(); 33 | } 34 | return stop; 35 | }, []); // Make sure the effect only runs once 36 | 37 | return { 38 | start, 39 | stop, 40 | }; 41 | } 42 | 43 | export default useFrame; 44 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "skipLibCheck": true, 6 | "jsxImportSource": "react", 7 | "noImplicitAny": false, 8 | "baseUrl": ".", 9 | "paths": { 10 | "@components/*": ["src/components/*"], 11 | "@src/*": ["src/*"], 12 | } 13 | } 14 | } --------------------------------------------------------------------------------