Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e89d7ece9 | |||
| 967a9bb4a4 |
@@ -1,20 +0,0 @@
|
||||
krill-parser.js
|
||||
krill.pegjs
|
||||
.eslintrc.json
|
||||
server.js
|
||||
tidal-sniffer.js
|
||||
*.jsx
|
||||
tunejs.js
|
||||
out/**
|
||||
postcss.config.js
|
||||
postcss.config.cjs
|
||||
tailwind.config.js
|
||||
tailwind.config.cjs
|
||||
vite.config.js
|
||||
/**/dist/**/*
|
||||
!**/*.mjs
|
||||
**/*.tsx
|
||||
**/*.ts
|
||||
**/*.json
|
||||
**/dev-dist
|
||||
**/dist
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": ["eslint:recommended"],
|
||||
"overrides": [],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["import"],
|
||||
"rules": {
|
||||
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
|
||||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Build and Deploy
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
deployments: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: "./website/dist"
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
@@ -7,18 +7,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18]
|
||||
node-version: [16, 17]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
- run: pnpm install
|
||||
- run: pnpm run format-check
|
||||
- run: pnpm run lint
|
||||
- run: pnpm test
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
dist
|
||||
out
|
||||
!docs/dist
|
||||
dist-*
|
||||
cabal-dev
|
||||
@@ -28,15 +27,4 @@ node_modules/
|
||||
repl-parcel
|
||||
mytunes.ts
|
||||
doc
|
||||
out
|
||||
.parcel-cache
|
||||
repl_old
|
||||
tutorial.rendered.mdx
|
||||
doc.json
|
||||
talk/public/EmuSP12
|
||||
talk/public/samples
|
||||
server/samples/old
|
||||
repl/stats.html
|
||||
coverage
|
||||
public/icons/apple-splash-*
|
||||
dev-dist
|
||||
.parcel-cache
|
||||
@@ -1,12 +0,0 @@
|
||||
*.md
|
||||
*.json
|
||||
*.yml
|
||||
*.
|
||||
**/out
|
||||
**/dist
|
||||
packages/mini/krill-parser.js
|
||||
packages/xen/tunejs.js
|
||||
paper
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
**/dev-dist
|
||||
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
||||
@@ -2,9 +2,5 @@
|
||||
"cSpell.words": [
|
||||
"subspan",
|
||||
"vals"
|
||||
],
|
||||
"yaml.schemas": {
|
||||
"https://json.schemastore.org/github-workflow.json": "file:///home/felix/projects/strudel/.github/workflows/deploy.yml"
|
||||
},
|
||||
"testing.automaticallyOpenPeekView": "never"
|
||||
]
|
||||
}
|
||||
@@ -12,8 +12,8 @@ To get in touch with the contributors, either
|
||||
|
||||
## Ask a Question
|
||||
|
||||
If you have any questions about strudel, make sure you've glanced through the
|
||||
[docs](https://strudel.tidalcycles.org/learn/) to find out if it answers your question.
|
||||
If you have any questions about strudel, make sure you've read the
|
||||
[tutorial](https://strudel.tidalcycles.org/tutorial/) to find out if it answers your question.
|
||||
If not, use one of the Communication Channels above!
|
||||
|
||||
Don't be afraid to ask! Your question might be of great value for other people too.
|
||||
@@ -29,10 +29,11 @@ If you made some music with strudel, you can give back some love and share what
|
||||
Your creation could also be part of the random selection in the REPL if you want.
|
||||
Use one of the Communication Channels listed above.
|
||||
|
||||
## Improve the Docs
|
||||
## Improve the Tutorial
|
||||
|
||||
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/learn/getting-started),
|
||||
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
|
||||
If you find some weak spots in the [tutorial](https://strudel.tidalcycles.org/),
|
||||
you are welcome to improve them by editing [this file](https://github.com/tidalcycles/strudel/blob/main/repl/src/tutorial/tutorial.mdx).
|
||||
This will even work without setting up a development environment, only a github account is required.
|
||||
|
||||
## Propose a Feature
|
||||
|
||||
@@ -49,43 +50,40 @@ Please check that it has not been reported before.
|
||||
To fix a bug that has been reported,
|
||||
|
||||
1. check that nobody else is already fixing it and respond to the issue to let people know you're on it
|
||||
2. fork the repository
|
||||
3. make sure you've setup the project (see below)
|
||||
4. hopefully fix the bug
|
||||
5. make sure the tests pass
|
||||
6. send a pull request
|
||||
3. fork the repository
|
||||
4. make sure you've setup the project (see below)
|
||||
5. hopefully fix the bug
|
||||
6. make sure the tests pass
|
||||
7. send a pull request
|
||||
|
||||
## Write Tests
|
||||
|
||||
There are still many tests that have not been written yet! Reading and writing tests is a great opportunity to get familiar with the codebase.
|
||||
You can find the tests in each package in the `test` folder. To run all tests, run `pnpm test` from the root folder.
|
||||
You can find the tests in each package in the `test` folder. To run all tests, run `npm test` from the root folder.
|
||||
|
||||
## Project Setup
|
||||
|
||||
To get the project up and running for development, make sure you have installed:
|
||||
|
||||
- [git](https://git-scm.com/)
|
||||
- [node](https://nodejs.org/en/) >= 18
|
||||
- [pnpm](https://pnpm.io/) (`npm i pnpm -g`)
|
||||
- git
|
||||
- node, preferably v16
|
||||
|
||||
then, do the following:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/tidalcycles/strudel.git && cd strudel
|
||||
pnpm i # install at root to symlink packages
|
||||
pnpm start # start repl
|
||||
npm i # install at root to symlink packages
|
||||
npx lerna bootstrap # install all dependencies in packages
|
||||
cd repl && npm i # install repl dependencies
|
||||
npm run start # start repl
|
||||
```
|
||||
|
||||
Those commands might look slightly different for your OS.
|
||||
Those commands might look slightly different for your OS.
|
||||
Please report any problems you've had with the setup instructions!
|
||||
|
||||
## Code Style
|
||||
|
||||
To make sure the code changes only where it should, we are using prettier to unify the code style.
|
||||
|
||||
- You can format all files at once by running `pnpm prettier` from the project root
|
||||
- Run `pnpm format-check` from the project root to check if all files are well formatted
|
||||
|
||||
If you use VSCode, you can
|
||||
|
||||
1. install [the prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
@@ -93,55 +91,19 @@ If you use VSCode, you can
|
||||
3. Choose "Configure Default Formatter..."
|
||||
4. Select prettier
|
||||
|
||||
## ESLint
|
||||
|
||||
To prevent unwanted runtime errors, this project uses [eslint](https://eslint.org/).
|
||||
|
||||
- You can check for lint errors by running `pnpm lint`
|
||||
|
||||
There are also eslint extensions / plugins for most editors.
|
||||
|
||||
## Running Tests
|
||||
|
||||
- Run all tests with `pnpm test`
|
||||
- Run all tests with UI using `pnpm test-ui`
|
||||
|
||||
## Running all CI Checks
|
||||
|
||||
When opening a PR, the CI runner will automatically check the code style and eslint, as well as run all tests.
|
||||
You can run the same check with `pnpm check`
|
||||
|
||||
## Package Workflow
|
||||
|
||||
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
|
||||
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
|
||||
allowing to develop multiple packages at the same time.
|
||||
When you run `npm i` on the root folder, [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces) will symlink all packages
|
||||
in the `node_modules` folder. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
|
||||
which allows developing multiple packages at the same time
|
||||
|
||||
## Package Publishing
|
||||
|
||||
To publish all packages that have been changed since the last release, run:
|
||||
|
||||
```sh
|
||||
npm login
|
||||
|
||||
# this will increment all the versions in package.json files of non private packages to selected versions
|
||||
npx lerna version --no-private
|
||||
|
||||
# publish all packages inside /packages using pnpm! don't use lerna to publish!!
|
||||
pnpm --filter "./packages/**" publish --dry-run
|
||||
|
||||
# the last command was only a dry-run, make sure everything looks ok, if yes, run the same command without flag
|
||||
```
|
||||
|
||||
To manually publish a single package, increase the version in the `package.json`, then run `pnpm publish`.
|
||||
Important: Always publish with `pnpm`, as `npm` does not support overriding main files in `publishConfig`, which is done in all the packages.
|
||||
|
||||
### New Packages
|
||||
|
||||
To add a new package, you have to publish it manually the first time, using:
|
||||
|
||||
```sh
|
||||
cd packages/<package-name> && pnpm publish --access public
|
||||
npx lerna publish
|
||||
```
|
||||
|
||||
## Have Fun
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
@@ -7,15 +7,17 @@
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
@@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
@@ -60,7 +72,7 @@ modification follow.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
@@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
@@ -633,29 +635,40 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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
|
||||
it under the terms of the GNU 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.
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
|
||||
[](https://github.com/tidalcycles/strudel/actions)
|
||||
|
||||
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is slowly stabilising, but please continue to tread carefully.
|
||||
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This is unstable software, please tread carefully.
|
||||
|
||||
- Try it here: <https://strudel.tidalcycles.org/>
|
||||
- Docs: <https://strudel.tidalcycles.org/learn/>
|
||||
- Tutorial: <https://strudel.tidalcycles.org/tutorial/>
|
||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
|
||||
|
||||
## Running Locally
|
||||
|
||||
After cloning the project, you can run the REPL locally:
|
||||
|
||||
```bash
|
||||
pnpm run setup
|
||||
pnpm run repl
|
||||
npm run setup
|
||||
npm run repl
|
||||
```
|
||||
|
||||
## Using Strudel In Your Project
|
||||
@@ -28,10 +27,8 @@ There are multiple npm packages you can use to use strudel, or only parts of it,
|
||||
- [`tone`](./packages/tone): bindings for Tone.js instruments and effects
|
||||
- [`osc`](./packages/osc): bindings to communicate via OSC
|
||||
- [`midi`](./packages/midi): webmidi bindings
|
||||
- [`serial`](./packages/serial): webserial bindings
|
||||
- [`tonal`](./packages/tonal): tonal functions
|
||||
- [`xen`](./packages/xen): microtonal / xenharmonic functions
|
||||
- ... [and there are more](./packages/)
|
||||
|
||||
Click on the package names to find out more about each one.
|
||||
|
||||
@@ -39,12 +36,6 @@ Click on the package names to find out more about each one.
|
||||
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
|
||||
|
||||
<a href="https://github.com/tidalcycles/strudel/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=tidalcycles/strudel" />
|
||||
</a>
|
||||
|
||||
Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
## Community
|
||||
|
||||
There is a #strudel channel on the TidalCycles discord: <https://discord.com/invite/HGEdXmRkzT>
|
||||
|
||||
|
Before Width: | Height: | Size: 150 KiB |
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.0d689283.css",
|
||||
"main.js": "/static/js/main.7e790d7f.js",
|
||||
"static/js/787.1c52cb78.chunk.js": "/static/js/787.1c52cb78.chunk.js",
|
||||
"static/media/logo.svg": "/static/media/logo.ac95051720b3dccfe511e0e02d8e1029.svg",
|
||||
"index.html": "/index.html",
|
||||
"main.0d689283.css.map": "/static/css/main.0d689283.css.map",
|
||||
"main.7e790d7f.js.map": "/static/js/main.7e790d7f.js.map",
|
||||
"787.1c52cb78.chunk.js.map": "/static/js/787.1c52cb78.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.0d689283.css",
|
||||
"static/js/main.7e790d7f.js"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Strudel REPL"/><title>Strudel REPL</title><script defer="defer" src="/static/js/main.7e790d7f.js"></script><link href="/static/css/main.0d689283.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunk_strudel_cycles_repl=self.webpackChunk_strudel_cycles_repl||[]).push([[787],{787:function(e,t,n){n.r(t),n.d(t,{getCLS:function(){return y},getFCP:function(){return g},getFID:function(){return C},getLCP:function(){return P},getTTFB:function(){return D}});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},f=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){f((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=p(),d(),s((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],f=o?null:c("paint",a);(o||f)&&(n=m(e,r,t),o&&a(o),s((function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,n(!0)}))}))})))},h=!1,T=-1,y=function(e,t){h||(g((function(e){T=e.value})),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},p=c("layout-shift",v);p&&(n=m(i,r,t),f((function(){p.takeRecords().map(v),n(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach((function(t){t(e)})),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},C=function(e,t){var n,a=l(),v=u("FID"),p=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},d=c("first-input",p);n=m(e,v,t),d&&f((function(){d.takeRecords().map(p),d.disconnect()}),!0),d&&s((function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,F(addEventListener),a=p,o.push(a),S()}))},k={},P=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e),n())},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){k[r.id]||(o.takeRecords().map(a),o.disconnect(),k[r.id]=!0,n(!0))};["keydown","click"].forEach((function(e){addEventListener(e,v,{once:!0,capture:!0})})),f(v,!0),s((function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,k[r.id]=!0,n(!0)}))}))}))}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",(function(){return setTimeout(t,0)}))}}}]);
|
||||
//# sourceMappingURL=787.1c52cb78.chunk.js.map
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license Fraction.js v4.2.0 05/03/2022
|
||||
* https://www.xarg.org/2014/03/rational-numbers-in-javascript/
|
||||
*
|
||||
* Copyright (c) 2021, Robert Eisele (robert@xarg.org)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Tone.js
|
||||
* @author Yotam Mann
|
||||
* @license http://opensource.org/licenses/MIT MIT License
|
||||
* @copyright 2014-2019 Yotam Mann
|
||||
*/
|
||||
|
||||
/** @license React v0.20.2
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico"><link rel="stylesheet" type="text/css" href="/tutorial/index.757e9f9d.css"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Strudel REPL"><title>Strudel Tutorial</title></head><body> <div id="root"></div> <noscript>You need to enable JavaScript to run this app.</noscript> <script src="/tutorial/index.a0cd6c24.js" defer></script> </body></html>
|
||||
@@ -0,0 +1,131 @@
|
||||
# Tidal Features in Strudel
|
||||
|
||||
## Basics
|
||||
|
||||
- [ ] drawLine
|
||||
- [ ] setcps
|
||||
- [ ] naming patterns? block based evaluation?
|
||||
- [ ] once
|
||||
- [x] silence
|
||||
- [x] hush
|
||||
- [ ] panic
|
||||
|
||||
## Concatenation
|
||||
|
||||
https://tidalcycles.org/docs/patternlib/tour/concatenation
|
||||
|
||||
- [x] cat: is synonym to fastcat in strudel, while [in tidal, cat is slowcat](https://tidalcycles.org/docs/patternlib/tour/concatenation#cat)
|
||||
- [x] fastcat
|
||||
- [x] timeCat: why is this camel case?
|
||||
- [ ] randcat
|
||||
- [x] append: but is like fastAppend in tidal
|
||||
- [ ] fastAppend
|
||||
- [ ] slowAppend
|
||||
- [ ] wedge
|
||||
- [ ] brak
|
||||
- [ ] flatpat
|
||||
|
||||
## Accumulation
|
||||
|
||||
- [ ] overlay => like stack? "The overlay function is similar to cat" => wrong?
|
||||
- [ ] `<>` operator (=== overlay)
|
||||
- [x] stack
|
||||
- [x] superimpose
|
||||
- [x] layer
|
||||
- [ ] steps ?
|
||||
- [x] iter
|
||||
- [x] iter' = iterBack
|
||||
|
||||
## Alteration
|
||||
|
||||
- [ ] range, rangex
|
||||
- [ ] quantise
|
||||
- [ ] ply
|
||||
- [x] stutter = echo
|
||||
- [ ] stripe, slowstripe
|
||||
- [ ] palindrome = every(2, rev)
|
||||
- [ ] trunc
|
||||
- [ ] linger
|
||||
- [x] chunk, chunk'
|
||||
- [ ] shuffle
|
||||
- [ ] scramble
|
||||
- [ ] rot
|
||||
- [ ] step / step'
|
||||
- [ ] lindenmeyer
|
||||
- [ ] spread / spreadf / fastspread
|
||||
- [ ] spreadChoose / spreadr
|
||||
|
||||
## conditions
|
||||
|
||||
- [x] every
|
||||
- [ ] every'
|
||||
- [ ] whenmod
|
||||
- [ ] sometimes, sometimesBy, someCycles, someCyclesBy
|
||||
- [ ] choose, chooseby, wchoose, wchooseby
|
||||
- [x] struct
|
||||
- [x] mask
|
||||
- [ ] sew
|
||||
- [ ] stitch
|
||||
- [ ] select, selectF
|
||||
- [ ] pickF
|
||||
- [ ] squeeze
|
||||
- [x] euclid, euclidLegato
|
||||
- [ ] euclidInv, euclidFull
|
||||
- [ ] ifp
|
||||
|
||||
## Time
|
||||
|
||||
- [x] fast
|
||||
- [x] fastGap
|
||||
- [x] slow
|
||||
- [ ] hurry
|
||||
- [ ] compress: is this compressSpan?
|
||||
- [ ] zoom
|
||||
- [ ] within
|
||||
- [x] off
|
||||
- [ ] rotL / rotR
|
||||
- [x] rev
|
||||
- [x] jux
|
||||
- [ ] juxBy
|
||||
- [ ] swingBy / swing
|
||||
- [ ] ghost
|
||||
- [ ] inside / outside
|
||||
|
||||
## Harmony & Melody
|
||||
|
||||
- [x] scale
|
||||
- [ ] scaleList
|
||||
- [ ] getScale
|
||||
- [ ] toScale
|
||||
- [ ] chordList
|
||||
- [ ] arpeggiate
|
||||
- [ ] arp
|
||||
|
||||
## Transitions
|
||||
|
||||
- [ ] anticipate / anticipateIn
|
||||
- [ ] clutch / clutchIn
|
||||
- [ ] histpan
|
||||
- [ ] interpolate / interpolateIn
|
||||
- [ ] jump / jumpIn / jumpIn' / jumpMod
|
||||
- [ ] wait / waitT
|
||||
- [ ] wash / washIn
|
||||
- [ ] xfade / xfadeIn
|
||||
|
||||
## Sampling
|
||||
|
||||
- [ ] chop
|
||||
- [ ] striate / striateBy
|
||||
- [ ] loopAt
|
||||
- [x] segment
|
||||
- [ ] discretise
|
||||
|
||||
## Randomness
|
||||
|
||||
- [ ] rand / irand
|
||||
- [ ] perlin / perlinWith / perlin2 / perlin2With
|
||||
|
||||
## Composition
|
||||
|
||||
- [ ] ur
|
||||
- [ ] seqP / seqPLoop
|
||||
@@ -1,26 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
public/samples
|
||||
@@ -1,22 +0,0 @@
|
||||
# Usage
|
||||
|
||||
```sh
|
||||
cd iclc2023/slides
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- ctrl+click or ctrl+(left|right) to switch slides
|
||||
|
||||
## Samples
|
||||
|
||||
To make sure all the samples are available, clone:
|
||||
|
||||
```sh
|
||||
cd public/samples
|
||||
git clone https://github.com/ritchse/tidal-drum-machines.git
|
||||
git clone https://github.com/tidalcycles/Dirt-Samples.git
|
||||
git clone https://github.com/felixroos/webaudiofontdata.git
|
||||
```
|
||||
|
||||
or use symlinks..
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Slides</title>
|
||||
</head>
|
||||
<body style="background-color: black">
|
||||
<div id="root">
|
||||
<span style="color: white">loading... </span>
|
||||
</div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "slides",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/rollup": "^2.3.0",
|
||||
"@nanostores/persistent": "^0.7.0",
|
||||
"@nanostores/react": "^0.4.1",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/react": "workspace:*",
|
||||
"@strudel.cycles/soundfonts": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/csound": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@uiw/codemirror-themes-all": "^4.19.8",
|
||||
"escodegen": "^2.0.0",
|
||||
"nanostores": "^0.7.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"vite-plugin-top-level-await": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 103 KiB |
@@ -1,46 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { atomone } from '@uiw/codemirror-themes-all';
|
||||
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
|
||||
import { initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import { TimeSpan } from '@strudel.cycles/core';
|
||||
|
||||
const initAudio = initAudioOnFirstClick();
|
||||
|
||||
function EventEditor({ code: initialCode }) {
|
||||
const [code, setCode] = useState(initialCode);
|
||||
const [view, setView] = useState();
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (view?.hasFocus) {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
/* if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
} */
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
let json = JSON.parse(code);
|
||||
if (!Array.isArray(json)) {
|
||||
json = [json];
|
||||
}
|
||||
await initAudio;
|
||||
json.forEach((value) => {
|
||||
const hap = new Hap(new TimeSpan(0, 1), new TimeSpan(0, 1), value);
|
||||
webaudioOutput(hap, 0.01, 1, 1);
|
||||
});
|
||||
} else if (e.key === '.') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[code, view],
|
||||
),
|
||||
);
|
||||
return <CodeMirror value={code} onChange={setCode} theme={atomone} onViewChanged={(v) => setView(v)} fontSize={32} />;
|
||||
}
|
||||
|
||||
export default EventEditor;
|
||||
@@ -1,39 +0,0 @@
|
||||
import EventEditor from './EventEditor.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`{ "s": "cp", "crush": 4 }`, 'Samples'],
|
||||
[`{ "s": "sawtooth", "note": "e3" }`, 'Oscillators'],
|
||||
[
|
||||
`[
|
||||
{ "s": "gm_epiano2", "note": "c4" },
|
||||
{ "s": "gm_epiano2", "note": "eb4" },
|
||||
{ "s": "gm_epiano2", "note": "g4" }
|
||||
]`,
|
||||
'Soundfonts',
|
||||
],
|
||||
[
|
||||
`{
|
||||
"s": "toys",
|
||||
"cutoff": 2000,
|
||||
"delay": 0.5,
|
||||
"crush": 4
|
||||
}`,
|
||||
'Effects',
|
||||
],
|
||||
];
|
||||
|
||||
function EventsDemo() {
|
||||
return (
|
||||
<Stepper
|
||||
steps={snippets.map(([code, label]) => (
|
||||
<div className="py-4 space-y-4">
|
||||
<h3>{label}</h3>
|
||||
<EventEditor code={code} />
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default EventsDemo;
|
||||
@@ -1,21 +0,0 @@
|
||||
//import {Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
//import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
//import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import js from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript';
|
||||
import hs from 'react-syntax-highlighter/dist/esm/languages/hljs/haskell';
|
||||
SyntaxHighlighter.registerLanguage('javascript', js);
|
||||
SyntaxHighlighter.registerLanguage('haskell', hs);
|
||||
|
||||
function Highlight({ code, language = 'javascript' }) {
|
||||
return (
|
||||
<div className="text-[32px] leading-10 rounded-xl overflow-hidden">
|
||||
<SyntaxHighlighter language={language} style={atomOneDark} customStyle={{ padding: '8px 10px' }}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Highlight;
|
||||
@@ -1,38 +0,0 @@
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`sound("bd [hh sd]")`, `sound(seq("bd", ["hh", "sd"]))`, 'Nested Sequences'], //
|
||||
[`sound("<bd sd>")`, `sound(cat("bd", "sd"))`, 'Cyclewise Sequences'], //
|
||||
[`sound("bd,hh")`, `sound(stack("bd", "hh"))`, 'Stack'], //
|
||||
[`sound("bd@3 sd@1")`, `sound(timeCat([3, "bd"], [1, "sd"]))`, 'Weighted Sequence'], //
|
||||
[`sound("{lt ht mt, bd sd}")`, `sound(polymeter(["lt","ht","mt"], ["bd", "sd"]))`, 'Polymeter'], //
|
||||
[`sound("{bd hh sd ht}%2")`, `sound(polymeterSteps(2, ["bd", "hh", "sd", "ht"]))`, 'Steps per Cycle'],
|
||||
];
|
||||
|
||||
function MiniComparison() {
|
||||
return (
|
||||
<>
|
||||
<h1>User Code: Mini Notation -> JavaScript</h1>
|
||||
<Stepper
|
||||
steps={snippets.map(([a, b, label]) => (
|
||||
<div className="py-4">
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/tidalcycles.svg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={a} hideHeader />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={b} hideHeader />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MiniComparison;
|
||||
@@ -1,50 +0,0 @@
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Highlight from './Highlight.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[
|
||||
<>
|
||||
{' '}
|
||||
<SlideRepl tune={`s("bd [hh sd]").crush(4).log()`} hideHeader />
|
||||
</>,
|
||||
'Logging Events',
|
||||
],
|
||||
[
|
||||
<>
|
||||
<Highlight
|
||||
code={`let pattern = s("bd [hh sd]").crush(4);
|
||||
let events = pattern.queryArc(0, 1);
|
||||
console.log(events.map(e => e.show()))`}
|
||||
language="javascript"
|
||||
/>
|
||||
<Highlight
|
||||
language="json"
|
||||
code={`[
|
||||
"0/1 -> 1/2 | s:bd crush:4",
|
||||
"1/2 -> 3/4 | s:hh crush:4",
|
||||
"3/4 -> 1/1 | s:sd crush:4"
|
||||
]`}
|
||||
/>
|
||||
</>,
|
||||
'Querying Events Manually',
|
||||
],
|
||||
];
|
||||
|
||||
function QueryDemo() {
|
||||
return (
|
||||
<>
|
||||
<img src="./img/queryflow.png" className="w-[800px]" />
|
||||
<Stepper
|
||||
steps={snippets.map(([snippet, label]) => (
|
||||
<div className="py-4 space-y-4">
|
||||
<h3>{label}</h3>
|
||||
{snippet}
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default QueryDemo;
|
||||
@@ -1,49 +0,0 @@
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
|
||||
import { prebake } from './prebake';
|
||||
import { atomone } from '@uiw/codemirror-themes-all';
|
||||
import blackscreen from '@strudel.cycles/react/src/themes/blackscreen';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
await evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
import('@strudel.cycles/csound'),
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
initAudioOnFirstClick();
|
||||
prebake();
|
||||
}
|
||||
|
||||
export function SlideRepl({
|
||||
tune,
|
||||
drawTime,
|
||||
punchcard,
|
||||
canvasHeight = 100,
|
||||
hideHeader = false,
|
||||
fontSize = 32,
|
||||
disabled = false,
|
||||
}) {
|
||||
return (
|
||||
<div className="not-prose rounded-xl overflow-hidden">
|
||||
<_MiniRepl
|
||||
hideHeader={hideHeader}
|
||||
tune={tune}
|
||||
hideOutsideView={true}
|
||||
drawTime={drawTime}
|
||||
punchcard={punchcard}
|
||||
canvasHeight={canvasHeight}
|
||||
fontSize={fontSize}
|
||||
theme={disabled ? blackscreen : atomone}
|
||||
// theme={themes[theme]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
import { useStore } from '@nanostores/react'; // or '@nanostores/preact'
|
||||
import useEvent from './useEvent';
|
||||
|
||||
function cx(...classes) {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
// https://vitejs.dev/guide/features.html#glob-import
|
||||
const slideImports = import.meta.glob('./slides/*.mdx');
|
||||
//const slideImports = import.meta.glob('./slides/*.mdx', { eager: true });
|
||||
console.log('load slides...', slideImports);
|
||||
|
||||
const loadedMDXFiles = await Promise.all(
|
||||
Object.entries(slideImports).map(async ([path, load]) => {
|
||||
//Object.entries(slideImports).map(async ([path, m]) => {
|
||||
const segments = path.split('/');
|
||||
const filename = segments[segments.length - 1].slice(0, -4); // expects .mdx at the end
|
||||
return [filename, (await load()).default];
|
||||
//return [filename, m.default];
|
||||
}),
|
||||
);
|
||||
console.log('load done!');
|
||||
|
||||
const order = [
|
||||
'cover',
|
||||
'whatIsStrudel',
|
||||
'history',
|
||||
'hs2js',
|
||||
|
||||
'usage',
|
||||
'swatch',
|
||||
'repl',
|
||||
//'repl-features',
|
||||
'viz',
|
||||
|
||||
'replflow',
|
||||
|
||||
// User Code
|
||||
'transpilation',
|
||||
'mininotation',
|
||||
// 'mini-comparison', // remove?
|
||||
// Patterns
|
||||
'patterns-events', // remove?
|
||||
// scheduler?
|
||||
// Output
|
||||
'events-audio', // remove?
|
||||
'outputs',
|
||||
// 'csound', // remove?
|
||||
//
|
||||
// pattern alignment?
|
||||
'bad',
|
||||
'good',
|
||||
'future',
|
||||
'end',
|
||||
];
|
||||
// examples swatch
|
||||
// draw examples!!
|
||||
|
||||
const slideEntries = order.map((name) => loadedMDXFiles.find(([file]) => file === name));
|
||||
|
||||
// current slide index is persisted, so it's safe to refresh the browser
|
||||
export const slideIndex = persistentAtom('slideIndex', '0');
|
||||
|
||||
export const prev = () => slideIndex.set((parseInt(slideIndex.get()) - 1 + slideEntries.length) % slideEntries.length);
|
||||
export const next = () => slideIndex.set((parseInt(slideIndex.get()) + 1) % slideEntries.length);
|
||||
|
||||
function Slides() {
|
||||
const activeIndex = parseInt(useStore(slideIndex));
|
||||
|
||||
/*useEvent('click', (e) => {
|
||||
if (!e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
const leftHalf = e.clientX / document.body.clientWidth < 0.5;
|
||||
if (leftHalf) {
|
||||
prev();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});*/
|
||||
/*useEvent('keydown', (e) => {
|
||||
if (e.key === 'Home') {
|
||||
prev();
|
||||
} else if (e.key === 'End') {
|
||||
next();
|
||||
}
|
||||
});*/
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden relative">
|
||||
{slideEntries.map(([path, Slide], i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cx(
|
||||
`bg-slate-900 w-full h-full flex justify-center absolute top-0 transition-all ease-in-out duration-500 overflow-auto`,
|
||||
i < activeIndex && '-translate-x-full',
|
||||
i > activeIndex && 'translate-x-full',
|
||||
)}
|
||||
>
|
||||
<div className="prose prose-invert w-[1400px] p-12 max-w-full" style={{ fontSize: '2em' }}>
|
||||
{Math.abs(i - activeIndex) <= 1 && <Slide />}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="text-white text-2xl text-bold cursor-pointer select-none">
|
||||
<div className="fixed left-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => prev()}>
|
||||
<
|
||||
</div>
|
||||
<div className="fixed right-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => next()}>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-0 w-full">
|
||||
<div className="h-[5px] bg-slate-500 w-full z-100">
|
||||
<div
|
||||
style={{ width: ((activeIndex / (slideEntries.length - 1)) * 100).toFixed(0) + '%' }}
|
||||
className="h-full bg-yellow-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Slides;
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
function Stepper({ steps }) {
|
||||
const [step, setStep] = useState(1);
|
||||
return (
|
||||
<div className="not-prose">
|
||||
{steps.slice(0, step).map((snippet, i) => {
|
||||
const isActive = i === step - 1;
|
||||
return (
|
||||
<div key={i} className={`border-l-4 pl-8 border-gray-500 ${isActive ? 'border-yellow-500' : ''}`}>
|
||||
{snippet}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{step < steps.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Stepper;
|
||||
@@ -1,73 +0,0 @@
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Highlight from './Highlight.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`sound "bd ~ [sd cp]"`, [`sound("bd ~ [sd cp]")`], 'Mini Notation'], //
|
||||
[`sound "bd ~ [sd cp]" # speed "1 2"`, [`sound("bd ~ [sd cp]").speed("1 2")`], 'Composing Patterns'],
|
||||
[
|
||||
`fast 2 $ sound "bd ~ [sd cp]"`,
|
||||
[`fast(2, sound("bd ~ [sd cp]"))`, `sound("bd ~ [sd cp]").fast(2)`],
|
||||
'Patterns Transformations',
|
||||
],
|
||||
[
|
||||
`speed "1 2 3" + "4 5" # s "bd"`,
|
||||
['speed("1 2 3".add("4 5")).s("bd")', '"1 2 3".add("4 5").speed().s("bd")'],
|
||||
'Pattern Arithmetic',
|
||||
],
|
||||
[
|
||||
`jux rev
|
||||
$ every 3 (fast 2)
|
||||
$ sound "bd sd"`,
|
||||
[
|
||||
`sound("bd sd")
|
||||
.every(3, fast("2"))
|
||||
.jux(rev)`,
|
||||
],
|
||||
'Higher order transformations (with partial application)',
|
||||
],
|
||||
[
|
||||
`jux rev
|
||||
$ every "<3 5>" (fast "1 2")
|
||||
$ sound "bd sd cp mt"`,
|
||||
[
|
||||
`sound("bd sd cp mt")
|
||||
.every("<3 5>", fast("1 2"))
|
||||
.jux(rev)`,
|
||||
],
|
||||
'Patterns all the way down',
|
||||
],
|
||||
];
|
||||
|
||||
function SyntaxComparison() {
|
||||
return (
|
||||
<>
|
||||
<h1>Haskell -> JavaScript</h1>
|
||||
<Stepper
|
||||
steps={snippets
|
||||
.map(([hs, js, label]) => (
|
||||
<div className="py-4 space-y-4">
|
||||
<h3>{label}</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex space-x-4">
|
||||
<img src="./img/haskell.png" className="h-10 mt-2" />
|
||||
<Highlight language="haskell" code={hs} />
|
||||
</div>
|
||||
<div>
|
||||
{js.map((c, j) => (
|
||||
<div className="flex space-x-4" key={j}>
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={c} hideHeader />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
.concat([<>Fluent Interface: Just chain all the things!</>])}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SyntaxComparison;
|
||||
@@ -1,84 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { atomone } from '@uiw/codemirror-themes-all';
|
||||
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import Highlight from './Highlight';
|
||||
import Stepper from './Stepper';
|
||||
import escodegen from 'escodegen';
|
||||
|
||||
function getTranspiled(code) {
|
||||
return transpiler(code, {
|
||||
addReturn: false,
|
||||
codegenOptions: {
|
||||
format: escodegen.FORMAT_MINIFY,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function TranspilationEditor({ code: initialCode }) {
|
||||
const [code, setCode] = useState(initialCode);
|
||||
const [transpiled, setTranspiled] = useState(getTranspiled(initialCode));
|
||||
const [view, setView] = useState();
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (view?.hasFocus) {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
/* if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
} */
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
const t = getTranspiled(code);
|
||||
setTranspiled(t);
|
||||
console.log('transpiled', t);
|
||||
} else if (e.key === '.') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[code, view],
|
||||
),
|
||||
);
|
||||
return (
|
||||
<div className="space-y-2 py-2">
|
||||
<CodeMirror
|
||||
value={code}
|
||||
onChange={(v) => {
|
||||
setCode(v);
|
||||
}}
|
||||
theme={atomone}
|
||||
onViewChanged={(v) => setView(v)}
|
||||
fontSize={32}
|
||||
/>
|
||||
<span>⬇️ transpiles to</span>
|
||||
<Highlight language="javascript" code={transpiled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TranspilationDemo() {
|
||||
return (
|
||||
<div className="not-prose space-y-8">
|
||||
<Stepper
|
||||
steps={[
|
||||
<>
|
||||
<img src="./img/transpileflow.png" className="h-[500px]" />
|
||||
<ul className="list-decimal pl-8">
|
||||
<li>Parse JS Code with acorn to get AST</li>
|
||||
<li>Modify AST with estree-walker</li>
|
||||
<li>Convert modified AST back to JS with escodegen</li>
|
||||
</ul>
|
||||
</>,
|
||||
<TranspilationEditor code={`"bd [hh sd]"`} />,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TranspilationDemo;
|
||||
@@ -1,21 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.cm-activeLine {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.cm-theme {
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--lineHighlight);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.cm-gutters {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply rounded-xl overflow-hidden;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import Slides from './Slides';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<Slides />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
import { registerSynthSounds, samples } from '@strudel.cycles/webaudio';
|
||||
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
|
||||
|
||||
export async function prebake() {
|
||||
// https://archive.org/details/SalamanderGrandPianoV3
|
||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||
await Promise.all([
|
||||
registerSynthSounds(),
|
||||
registerSoundfonts(`./samples/webaudiofontdata/sound/`),
|
||||
/* samples(`./piano.json`, `./piano/`, { prebake: true }),
|
||||
samples(`./vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
|
||||
*/
|
||||
//samples(`./tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
|
||||
samples(`./tidal-drum-machines.json`, './samples/tidal-drum-machines/machines/', {
|
||||
prebake: true,
|
||||
tag: 'drum-machines',
|
||||
}) /*
|
||||
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, tag: 'drum-machines' }), */,
|
||||
// samples('github:tidalcycles/Dirt-Samples/master'),
|
||||
samples('./Dirt-Samples.json', './samples/Dirt-Samples/'),
|
||||
]);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# What's bad
|
||||
|
||||
- Dynamic Typing in JS
|
||||
- Fractions and Hot Paths
|
||||
- Verbose function composition
|
||||
- Method-chaining tradeoffs
|
||||
@@ -1,7 +0,0 @@
|
||||
<div className="flex flex-col justify-between h-full overflow-hidden max-h-screen space-y-8">
|
||||
<h2 className="m-0">Strudel: live coding patterns on the Web</h2>
|
||||
<div className="justify-center flex overflow-hidden not-prose">
|
||||
<img src="./img/strudel_icon.png" className="h-full" />
|
||||
</div>
|
||||
<span>Felix Roos, Alex McLean</span>
|
||||
</div>
|
||||
@@ -1,65 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# CSound Example
|
||||
|
||||
## Loading ORC instruments
|
||||
|
||||
<SlideRepl
|
||||
tune={`// livecode.orc by Steven Yi
|
||||
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
|
||||
|
||||
stack(
|
||||
note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand'))
|
||||
.cutoff(sine.range(500,2000).round().slow(16))
|
||||
.euclidLegato(3,8).csound('FM1'),
|
||||
|
||||
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass')
|
||||
.gain("1 4 1 4"),
|
||||
|
||||
note("0 7 [4 3] 2".fast(2/3)
|
||||
.off(".25 .125", add("<2 4 -3 -1>"))
|
||||
.slow(2).scale('A4 minor'))
|
||||
.legato(.25).csound('SynHarp'),
|
||||
|
||||
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
|
||||
)`}
|
||||
hideHeader
|
||||
/>
|
||||
|
||||
## Synthesis
|
||||
|
||||
<SlideRepl
|
||||
tune={`await loadCsound\`
|
||||
instr CoolSynth
|
||||
iduration = p3
|
||||
ifreq = p4
|
||||
igain = p5
|
||||
ioct = octcps(ifreq)
|
||||
|
||||
kpwm = oscili(.05, 8)
|
||||
asig = vco2(igain, ifreq, 4, .5 + kpwm)
|
||||
asig += vco2(igain, ifreq * 2)
|
||||
|
||||
idepth = 2
|
||||
acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope
|
||||
asig = zdf_2pole(asig, cpsoct(ioct + acut + 2), 0.5)
|
||||
|
||||
iattack = .01
|
||||
isustain = .5
|
||||
idecay = .1
|
||||
irelease = .1
|
||||
asig *= linsegr:a(0, iattack, 1, idecay, isustain, iduration, isustain, irelease, 0)
|
||||
|
||||
out(asig, asig)
|
||||
|
||||
endin\`
|
||||
|
||||
"<0 2 [4 6](3,4,2) 3\*2>"
|
||||
.off(1/4, add(2))
|
||||
.off(1/2, add(6))
|
||||
.scale('D minor')
|
||||
.note()
|
||||
//.pianoroll()
|
||||
.csound('CoolSynth')`}
|
||||
hideHeader
|
||||
/>
|
||||
@@ -1 +0,0 @@
|
||||
# Strudel Docs
|
||||
@@ -1,4 +0,0 @@
|
||||
# Thanks
|
||||
|
||||
- ICLC organizers
|
||||
- Tidal Community
|
||||
@@ -1,7 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
import Highlight from '../Highlight.jsx';
|
||||
import EventsDemo from '../EventsDemo.jsx';
|
||||
|
||||
# Webaudio Output: Values -> Sound
|
||||
|
||||
<EventsDemo />
|
||||
@@ -1,15 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# Pattern Example
|
||||
|
||||
<SlideRepl
|
||||
tune={`"<0 2 [4 6](3,4,1) 3>"
|
||||
.off(1/4, add(2))
|
||||
.off(1/2, add(6))
|
||||
.scale('D minor')
|
||||
.legato(.25)
|
||||
.note().s("sawtooth square")
|
||||
.delay(.8).delaytime(.125)`}
|
||||
hideHeader
|
||||
punchcard
|
||||
/>
|
||||
@@ -1,10 +0,0 @@
|
||||
# Future Outlook
|
||||
|
||||
- (slow) community growth
|
||||
- block based evaluation
|
||||
- live collaboration
|
||||
- more audio backends
|
||||
- interface experiments, e.g.:
|
||||
- alternative syntax, visual notations
|
||||
- time scrubbing
|
||||
- ui controls
|
||||
@@ -1,9 +0,0 @@
|
||||
# What's good
|
||||
|
||||
- Zero-install, multi-platform
|
||||
- works (surprisingly) well on mobile devices
|
||||
- visual feedback good for teaching + learning
|
||||
- loads of potential contributors
|
||||
- instant pattern sharing
|
||||
- two-way flow of features between strudel and tidal
|
||||
- full mini-notation in hydra and gibber
|
||||
@@ -1,3 +0,0 @@
|
||||
# The journey
|
||||
|
||||

|
||||
@@ -1,3 +0,0 @@
|
||||
import SyntaxComparison from '../SyntaxComparison.jsx';
|
||||
|
||||
<SyntaxComparison />
|
||||
@@ -1,5 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
import Highlight from '../Highlight.jsx';
|
||||
import MiniComparison from '../MiniComparison.jsx';
|
||||
|
||||
<MiniComparison />
|
||||
@@ -1,29 +0,0 @@
|
||||
import Highlight from '../Highlight';
|
||||
import Stepper from '../Stepper';
|
||||
|
||||
# User Code: Mini Notation
|
||||
|
||||
<img src="./img/pegflow.png" />
|
||||
|
||||
<Stepper
|
||||
steps={[
|
||||
<ul className="list-decimal pl-8">
|
||||
<li>Parse Mini Notation to get AST</li>
|
||||
<li>walk AST and construct function calls</li>
|
||||
</ul>,
|
||||
<div className="space-y-2 py-2">
|
||||
<Highlight code={`mini('bd [hh sd]')`} />
|
||||
<span>⬇️ resulting calls</span>
|
||||
<Highlight
|
||||
code={`seq(
|
||||
reify('bd').withLocation([1,1,1], [1,4,4]),
|
||||
seq(
|
||||
reify('hh').withLocation([1,5,5], [1,8,8]),
|
||||
reify('sd').withLocation([1,8,8], [1,10,10]),
|
||||
)
|
||||
)`}
|
||||
/>
|
||||
<span>➡️ location can be used for highlighting</span>
|
||||
</div>,
|
||||
]}
|
||||
/>
|
||||
@@ -1,7 +0,0 @@
|
||||
# What can I pattern?
|
||||
|
||||
- Default: Web Audio API
|
||||
- OSC via osc-js, e.g. SuperDirt
|
||||
- Csound via the Csound WebAssembly build
|
||||
- MIDI via WebMIDI
|
||||
- Serial via WebSerial, e.g. for robot dancing
|
||||
@@ -1,5 +0,0 @@
|
||||
import QueryDemo from '../QueryDemo.jsx';
|
||||
|
||||
# Querying: From Patterns to Events
|
||||
|
||||
<QueryDemo />
|
||||
@@ -1,12 +0,0 @@
|
||||
# REPL: Features
|
||||
|
||||
- Visual Feedback
|
||||
- Example shuffler (Hydra-style)
|
||||
- Tutorial + reference
|
||||
- Shareable links
|
||||
- Sound Browser
|
||||
- Offline Support (+ installable PWA)
|
||||
- Outputs: Web Audio, MIDI, OSC, CSound, Serial, ..
|
||||
- Themes
|
||||
|
||||

|
||||
@@ -1,7 +0,0 @@
|
||||
# REPL - live coding editor
|
||||
|
||||
[strudel.tidalcycles.org](https://strudel.tidalcycles.org/)
|
||||
|
||||
{/* <iframe className="w-full h-[600px]" src="https://strudel.tidalcycles.org/#c2FtcGxlcyh7CiAgYmQ6IFsnYmQvQlQwQUFEQS53YXYnLCdiZC9CVDBBQUQwLndhdicsJ2JkL0JUMEEwREEud2F2JywnYmQvQlQwQTBEMy53YXYnLCdiZC9CVDBBMEQwLndhdicsJ2JkL0JUMEEwQTcud2F2J10sCiAgc2Q6IFsnc2Qvcnl0bS0wMS1jbGFzc2ljLndhdicsJ3NkL3J5dG0tMDAtaGFyZC53YXYnXSwKICBoaDogWydoaDI3LzAwMF9oaDI3Y2xvc2VkaGgud2F2JywnaGgvMDAwX2hoM2Nsb3NlZGhoLndhdiddLAp9LCAnZ2l0aHViOnRpZGFsY3ljbGVzL0RpcnQtU2FtcGxlcy9tYXN0ZXIvJyk7CnN0YWNrKAogIHMoImJkLFt%2BIDxzZCEzIHNkKDMsNCwyKT5dLGhoKjQiKSAvLyBkcnVtcwogIC5zcGVlZChwZXJsaW4ucmFuZ2UoLjcsMSkpIC8vIHJhbmRvbSBzYW1wbGUgc3BlZWQgdmFyaWF0aW9uCiAgLy8uaHVzaCgpCiAgLCI8YTEgYjEqMiBhMSgzLDgpIGUyPiIgLy8gYmFzc2xpbmUKICAub2ZmKDEvOCx4PT54LmFkZCgxMikuZGVncmFkZUJ5KC41KSkgLy8gcmFuZG9tIG9jdGF2ZSBqdW1wcwogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLnN1cGVyaW1wb3NlKGFkZCguMDUpKSAvLyBhZGQgc2Vjb25kLCBzbGlnaHRseSBkZXR1bmVkIHZvaWNlCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMTUpLnN1c3RhaW4oMCkgLy8gbWFrZSBlYWNoIG5vdGUgb2YgZXF1YWwgbGVuZ3RoCiAgLnMoJ3Nhd3Rvb3RoJykgLy8gd2F2ZWZvcm0KICAuZ2FpbiguNCkgLy8gdHVybiBkb3duCiAgLmN1dG9mZihzaW5lLnNsb3coNykucmFuZ2UoMzAwLDUwMDApKSAvLyBhdXRvbWF0ZSBjdXRvZmYKICAvLy5odXNoKCkKICAsIjxBbTchMyA8RW03IEU3YjEzIEVtNyBFYm03YjU%2BPiIudm9pY2luZ3MoJ2xlZnRoYW5kJykgLy8gY2hvcmRzCiAgLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4wNCkpIC8vIGFkZCBzZWNvbmQsIHNsaWdodGx5IGRldHVuZWQgdm9pY2UKICAuYWRkKHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tIHBpdGNoIHZhcmlhdGlvbgogIC5uKCkgLy8gd3JhcCBpbiAibiIKICAucygnc2F3dG9vdGgnKSAvLyB3YXZlZm9ybQogIC5nYWluKC4xNikgLy8gdHVybiBkb3duCiAgLmN1dG9mZig1MDApIC8vIGZpeGVkIGN1dG9mZgogIC5hdHRhY2soMSkgLy8gc2xvd2x5IGZhZGUgaW4KICAvLy5odXNoKCkKICAsImE0IGM1IDxlNiBhNj4iLnN0cnVjdCgieCg1LDgpIikKICAuc3VwZXJpbXBvc2UoeD0%2BeC5hZGQoLjA0KSkgLy8gYWRkIHNlY29uZCwgc2xpZ2h0bHkgZGV0dW5lZCB2b2ljZQogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMSkuc3VzdGFpbigwKSAvLyBtYWtlIG5vdGVzIHNob3J0CiAgLnMoJ3RyaWFuZ2xlJykgLy8gd2F2ZWZvcm0KICAuZGVncmFkZUJ5KHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tbHkgY29udHJvbGxlZCByYW5kb20gcmVtb3ZhbCA6KQogIC5lY2hvV2l0aCg0LC4xMjUsKHgsbik9PnguZ2FpbiguMTUqMS8obisxKSkpIC8vIGVjaG8gbm90ZXMKICAvLy5odXNoKCkKKQogIC5zbG93KDEuNCk%3D" /> */}
|
||||
|
||||

|
||||
@@ -1,5 +0,0 @@
|
||||
# REPL: System Overview
|
||||
|
||||
<center>
|
||||
<img src="./img/strudelflow.png" className="w-[900px]" />
|
||||
</center>
|
||||
@@ -1,3 +0,0 @@
|
||||
# Strudel Docs
|
||||
|
||||

|
||||
@@ -1,5 +0,0 @@
|
||||
# Pattern Swatches
|
||||
|
||||
fork strudel - publish your own patterns with a fixed version
|
||||
|
||||

|
||||
@@ -1,5 +0,0 @@
|
||||
import TranspilationDemo from '../TranspilationDemo';
|
||||
|
||||
# User Code: Transpilation
|
||||
|
||||
<TranspilationDemo />
|
||||
@@ -1,14 +0,0 @@
|
||||
import Highlight from '../Highlight.jsx';
|
||||
|
||||
# Ways to use Strudel
|
||||
|
||||
- "Strudel REPL" at [strudel.tidalcycles.org](https://strudel.tidalcycles.org)
|
||||
- strudel on [flok.cc](https://flok.cc/) for collaboration
|
||||
- `@strudel.cycles/*` npm packages for your own projects
|
||||
- Embeddable [Web Component](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelcyclesembed)
|
||||
- Browser Plugin for [Mastodon](https://github.com/mortuosplango/topfenstrudel) + Discourse Plugin:
|
||||
|
||||
<div className="flex">
|
||||
<img src="./img/topfenstrudel.png" />
|
||||
<img src="./img/discourse.png" />
|
||||
</div>
|
||||
@@ -1,20 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# REPL: Visual Feedback
|
||||
|
||||
Highlighting + Piano Roll
|
||||
|
||||
<SlideRepl
|
||||
hideHeader
|
||||
punchcard
|
||||
canvasHeight={300}
|
||||
fontSize={24}
|
||||
tune={`// Steve Reich - Piano Phase (1967)
|
||||
note("E4 F#4 B4 C#5 D5 F#4 E4 C#5 B4 F#4 D5 C#5")
|
||||
.layer(
|
||||
x=>x.color('#00ff0050').pan(0).fast(1), // left speaker
|
||||
x=>x.color('#ff00ff50').pan(1).fast(1.01) // right speaker
|
||||
)
|
||||
.slow(2).room(.5)
|
||||
`}
|
||||
/>
|
||||
@@ -1,28 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# What is Strudel
|
||||
|
||||
- TidalCycles faithfully ported to JavaScript
|
||||
- zero-install live coding environment for the browser
|
||||
- free & open source, modular, hackable, sharable, embeddable:
|
||||
|
||||
<SlideRepl
|
||||
hideHeader
|
||||
fontSize={24}
|
||||
tune={`// froos - freakuency - 2023/03/29
|
||||
stack(
|
||||
stack(
|
||||
s("bd, ~ <rim cp>,~ hh*2").off(1/8, x=>x.speed(2).gain(.5)).fast(2),
|
||||
s("oh*6").end(.01).speed(.5).room(.5).fast("1 2")
|
||||
).bank('RolandTR909'),
|
||||
//
|
||||
note("[c a f e](<3 5>,8,<0 1>)".add("0,.1").sub(24)).s('sawtooth').lpf(500),
|
||||
//
|
||||
"[~ <Cm7 Fm7 Bb7 Db7>]*2".voicings('lefthand').note().legato(.2)
|
||||
.echoWith(6, .1, (p,n)=>p.add(note(n*.6)))
|
||||
.lpf(sine.range(300,1000).slow(8)).lpq(6)
|
||||
.add(note(perlin.range(0,.8))).s('gm_epiano2')
|
||||
.mask("<0 1>")
|
||||
).restart("<1@3 1*[4 [8 16]]>")
|
||||
.slow(2)`}
|
||||
/>
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function useEvent(name, onTrigger, useCapture = false) {
|
||||
useEffect(() => {
|
||||
document.addEventListener(name, onTrigger, useCapture);
|
||||
return () => {
|
||||
document.removeEventListener(name, onTrigger, useCapture);
|
||||
};
|
||||
}, [onTrigger]);
|
||||
}
|
||||
|
||||
export default useEvent;
|
||||
@@ -1,26 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'../../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// codemirror-theme settings
|
||||
background: 'var(--background)',
|
||||
lineBackground: 'var(--lineBackground)',
|
||||
foreground: 'var(--foreground)',
|
||||
caret: 'var(--caret)',
|
||||
selection: 'var(--selection)',
|
||||
selectionMatch: 'var(--selectionMatch)',
|
||||
gutterBackground: 'var(--gutterBackground)',
|
||||
gutterForeground: 'var(--gutterForeground)',
|
||||
gutterBorder: 'var(--gutterBorder)',
|
||||
lineHighlight: 'var(--lineHighlight)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import mdx from '@mdx-js/rollup';
|
||||
import gfm from 'remark-gfm';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
|
||||
const options = {
|
||||
// See https://mdxjs.com/advanced/plugins
|
||||
remarkPlugins: [gfm],
|
||||
rehypePlugins: [],
|
||||
};
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
mdx(options),
|
||||
topLevelAwait({
|
||||
// The export name of top-level await promise for each chunk module
|
||||
promiseExportName: '__tla',
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
promiseImportName: (i) => `__tla_${i}`,
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
target: 'esnext', //browsers can handle the latest ES features
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
// this barrel export is currently only used to find undocumented exports
|
||||
export * from './packages/core/index.mjs';
|
||||
export * from './packages/csound/index.mjs';
|
||||
export * from './packages/embed/index.mjs';
|
||||
export * from './packages/eval/index.mjs';
|
||||
export * from './packages/midi/index.mjs';
|
||||
export * from './packages/mini/index.mjs';
|
||||
export * from './packages/osc/index.mjs';
|
||||
export * from './packages/react/index.mjs';
|
||||
export * from './packages/serial/index.mjs';
|
||||
export * from './packages/soundfonts/index.mjs';
|
||||
export * from './packages/tonal/index.mjs';
|
||||
export * from './packages/tone/index.mjs';
|
||||
export * from './packages/transpiler/index.mjs';
|
||||
export * from './packages/webaudio/index.mjs';
|
||||
export * from './packages/webdirt/index.mjs';
|
||||
export * from './packages/xen/index.mjs';
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"source": {
|
||||
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
||||
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
|
||||
},
|
||||
"plugins": ["plugins/markdown"],
|
||||
"opts": {
|
||||
"destination": "./out/",
|
||||
"recurse": true
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent",
|
||||
"npmClient": "pnpm",
|
||||
"useWorkspaces": true
|
||||
"version": "independent"
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# my-patterns
|
||||
|
||||
This directory can be used to save your own patterns, which then get
|
||||
made into a pattern swatch.
|
||||
|
||||
Example: <https://felixroos.github.io/strudel/swatch/>
|
||||
|
||||
## deploy
|
||||
|
||||
### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git)
|
||||
|
||||
### 2. clone your fork to your machine `git clone https://github.com/<your-username>/strudel.git strudel && cd strudel`
|
||||
|
||||
### 3. create a separate branch like `git branch patternuary && git checkout patternuary`
|
||||
|
||||
### 4. save one or more .txt files in the my-patterns folder
|
||||
|
||||
### 5. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
|
||||
|
||||
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`, like this
|
||||
|
||||
```js
|
||||
const site = 'https://<your-username>.github.io';
|
||||
const base = '/strudel';
|
||||
```
|
||||
|
||||
### 7. commit & push the changes
|
||||
|
||||
```sh
|
||||
git add . && git commit -m "site config" && git push --set-upstream origin
|
||||
```
|
||||
|
||||
### 8. deploy to github pages
|
||||
|
||||
- go to settings -> pages and select "Github Actions" as source
|
||||
- go to settings -> environments -> github-pages and press the edit button next to `main` and type in `patternuary` (under "Deployment branches")
|
||||
- go to Actions -> `Build and Deploy` and click `Run workflow` with branch `patternuary`
|
||||
|
||||
### 9. view your patterns at `<your-username>.github.io/strudel/swatch/`
|
||||
|
||||
Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
|
||||
|
||||
### 10. optional: automatic deployment
|
||||
|
||||
If you want to automatically deploy your site on push, go to `deploy.yml` and change `workflow_dispatch` to `push`.
|
||||
|
||||
## running locally
|
||||
|
||||
- install dependencies with `npm run setup`
|
||||
- run dev server with `npm run repl` and open `http://localhost:3000/strudel/swatch/`
|
||||
|
||||
## tests fail?
|
||||
|
||||
Your tests might fail if the code does not follow prettiers format.
|
||||
In that case, run `npm run codeformat`. To disable that, remove `npm run format-check` from `test.yml`
|
||||
|
||||
## updating your fork
|
||||
|
||||
To update your fork, you can pull the main branch and merge it into your `patternuary` branch.
|
||||
@@ -1,33 +1,20 @@
|
||||
{
|
||||
"name": "@strudel.cycles/monorepo",
|
||||
"version": "0.5.0",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "Port of tidalcycles to javascript",
|
||||
"main": "strudel.mjs",
|
||||
"scripts": {
|
||||
"setup": "pnpm i",
|
||||
"pretest": "npm run jsdoc-json",
|
||||
"prebuild": "npm run jsdoc-json",
|
||||
"prestart": "npm run jsdoc-json",
|
||||
"test": "npm run pretest && vitest run --version",
|
||||
"test-ui": "npm run pretest && vitest --ui",
|
||||
"test-coverage": "npm run pretest && vitest --coverage",
|
||||
"snapshot": "npm run pretest && vitest run -u --silent",
|
||||
"repl": "npm run prestart && cd website && npm run dev",
|
||||
"start": "npm run prestart && cd website && npm run dev",
|
||||
"dev": "npm run prestart && cd website && npm run dev",
|
||||
"build": "npm run prebuild && cd website && npm run build",
|
||||
"preview": "cd website && npm run preview",
|
||||
"test": "npm run test --workspaces --if-present",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"setup": "npm i && npm run bootstrap && cd repl && npm i",
|
||||
"repl": "cd repl && npm run start",
|
||||
"osc": "cd packages/osc && npm run server",
|
||||
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
|
||||
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
|
||||
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
|
||||
"lint": "eslint . --ext mjs,js --quiet",
|
||||
"codeformat": "prettier --write .",
|
||||
"format-check": "prettier --check .",
|
||||
"report-undocumented": "npm run jsdoc-json && node undocumented.mjs > undocumented.json",
|
||||
"check": "npm run format-check && npm run lint && npm run test",
|
||||
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
|
||||
"build": "cd repl && npm run build"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
@@ -40,35 +27,14 @@
|
||||
"algorave"
|
||||
],
|
||||
"author": "Alex McLean <alex@slab.org> (https://slab.org)",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*",
|
||||
"acorn": "^8.8.1",
|
||||
"dependency-tree": "^9.0.0",
|
||||
"vitest": "^0.25.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.25.7",
|
||||
"c8": "^7.12.0",
|
||||
"canvas": "^2.11.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"events": "^3.3.0",
|
||||
"gh-pages": "^4.0.0",
|
||||
"jsdoc": "^3.6.10",
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"jsdoc-to-markdown": "^7.1.1",
|
||||
"lerna": "^6.5.1",
|
||||
"prettier": "^2.8.1",
|
||||
"rollup-plugin-visualizer": "^5.8.1"
|
||||
"lerna": "^4.0.0",
|
||||
"mocha": "^9.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Packages
|
||||
|
||||
Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles).
|
||||
|
||||
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
|
||||
|
||||
This is a graphical view of all the packages: [full screen](https://raw.githubusercontent.com/tidalcycles/strudel/main/dependencies.svg)
|
||||
|
||||

|
||||
|
||||
Generated with
|
||||
|
||||
```sh
|
||||
npx depcruise --include-only "^packages" -X "node_modules" --output-type dot packages | dot -T svg > dependencygraph.svg
|
||||
```
|
||||