Merge branch 'main' of ssh://codeberg.org/uzu/strudel
This commit is contained in:
commit
b253d437d3
12 changed files with 9 additions and 372 deletions
|
|
@ -201,7 +201,7 @@ interfaces.
|
||||||
|
|
||||||
The Strudel REPL is available at <https://strudel.cc>,
|
The Strudel REPL is available at <https://strudel.cc>,
|
||||||
including an interactive tutorial. The repository is at
|
including an interactive tutorial. The repository is at
|
||||||
<https://github.com/tidalcycles/strudel>, all the code is open source
|
<https://codeberg.org/uzu/strudel/releases>, all the code is open source
|
||||||
under the GPL-3.0 License.
|
under the GPL-3.0 License.
|
||||||
|
|
||||||
# Acknowledgments
|
# Acknowledgments
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ For the future, it is planned to integrate alternative sound engines such as Gli
|
||||||
# Links
|
# Links
|
||||||
|
|
||||||
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
|
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
|
||||||
The repository is at <https://github.com/tidalcycles/strudel>, all the code is open source under the GPL-3.0 License.
|
The repository is at <https://codeberg.org/uzu/strudel/releases>, all the code is open source under the GPL-3.0 License.
|
||||||
|
|
||||||
# Acknowledgments
|
# Acknowledgments
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -451,7 +451,7 @@ While Haskell's type system makes it a great language for the ongoing developmen
|
||||||
# Links
|
# Links
|
||||||
|
|
||||||
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
|
The Strudel REPL is available at <https://strudel.cc>, including an interactive tutorial.
|
||||||
The repository is at <https://github.com/tidalcycles/strudel>, all the code is open source under the AGPL-3.0 License.
|
The repository is at <https://codeberg.org/uzu/strudel>, all the code is open source under the AGPL-3.0 License.
|
||||||
|
|
||||||
# Acknowledgments
|
# Acknowledgments
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel).
|
Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel).
|
||||||
|
|
||||||
To understand how those pieces connect, refer to the [Technical Manual](https://codeberg.org/uzu/strudel/src/branch/main/technical-manual.md) or the individual READMEs.
|
To understand how those pieces connect, refer to the [Technical Manual](https://codeberg.org/uzu/strudel/wiki/Technical-Manual) or the individual READMEs.
|
||||||
|
|
|
||||||
|
|
@ -884,7 +884,7 @@ describe('Pattern', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('Doesnt drop haps in the 9th cycle', () => {
|
it('Doesnt drop haps in the 9th cycle', () => {
|
||||||
// fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
|
// fixed with https://codeberg.org/uzu/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
|
||||||
expect(sequence(1, 2, 3).ply(2).early(8).firstCycle().length).toBe(6);
|
expect(sequence(1, 2, 3).ply(2).early(8).firstCycle().length).toBe(6);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ You can also pin the version like this:
|
||||||
```
|
```
|
||||||
|
|
||||||
This has the advantage that your code will always work, regardless of potential breaking changes in the strudel codebase.
|
This has the advantage that your code will always work, regardless of potential breaking changes in the strudel codebase.
|
||||||
See [releases](https://github.com/tidalcycles/strudel/releases) for the latest versions.
|
See [releases](https://codeberg.org/uzu/strudel/releases) for the latest versions.
|
||||||
|
|
||||||
## Use Web Component
|
## Use Web Component
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
This document introduces you to Strudel in a technical sense. If you just want to *use* Strudel, have a look at the [Tutorial](https://strudel.tidalcycles.org/tutorial/).
|
|
||||||
|
|
||||||
## Strudel Packages
|
|
||||||
|
|
||||||
There are different packages for different purposes. They..
|
|
||||||
|
|
||||||
- split up the code into smaller chunks
|
|
||||||
- can be selectively used to implement some sort of time based system
|
|
||||||
|
|
||||||
Please refer to the individual README files in the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages)
|
|
||||||
|
|
||||||
## REPL
|
|
||||||
|
|
||||||
The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library.
|
|
||||||
|
|
||||||
More info in the [REPL README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl/README.md)
|
|
||||||
|
|
||||||
# High Level Overview
|
|
||||||
|
|
||||||
<img src="https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/strudelflow.png" width="600" />
|
|
||||||
|
|
||||||
## 1. End User Code
|
|
||||||
|
|
||||||
The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://codeberg.org/uzu/strudel/src/branch/main/packages/eval#strudelcycleseval) evaluates the user code
|
|
||||||
after a transpilation step, which resolves the syntax sugar. If you don't want the syntax sugar, you can omit the eval package and call the native javascript `eval` instead.
|
|
||||||
|
|
||||||
### 🍭 Syntax Sugar
|
|
||||||
|
|
||||||
JavaScript Transpilation = converting valid JavaScript to valid JavaScript:
|
|
||||||
|
|
||||||
```js
|
|
||||||
"c3 [e3 g3]".fast(2)
|
|
||||||
```
|
|
||||||
|
|
||||||
becomes
|
|
||||||
|
|
||||||
```js
|
|
||||||
mini('c3 [e3 g3]')
|
|
||||||
.withMiniLocation([1, 0, 0], [1, 11, 11]) // source location
|
|
||||||
.fast(2);
|
|
||||||
```
|
|
||||||
|
|
||||||
- double quoted strings and backtick strings are turned into `mini` calls (single quoted strings are left as is)
|
|
||||||
- The source location is added by chaining `withMiniLocation`, which enables the real time highlighting later
|
|
||||||
- (psuedo) variable names that look like notes (like `c4`, `bb2` or `fs3`) are turned into strings
|
|
||||||
- support for top level await
|
|
||||||
- operator overloading could be implemented in the future
|
|
||||||
|
|
||||||
This is how it works:
|
|
||||||
|
|
||||||
<img src="https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/shiftflow.png" width="800" />
|
|
||||||
|
|
||||||
- The user code is parsed with a [shift parser](https://github.com/shapesecurity/shift-parser-js), generating an AST
|
|
||||||
- The AST is transformed to resolve the syntax sugar
|
|
||||||
- The AST is used to generate code again (shift-codegen)
|
|
||||||
|
|
||||||
Shift will most likely be replaced with acorn in the future, see https://codeberg.org/uzu/strudel/issues/174
|
|
||||||
|
|
||||||
### Mini Notation
|
|
||||||
|
|
||||||
Another important part of the user code is the mini notation, which allows to express rhythms in a short manner.
|
|
||||||
|
|
||||||
- the mini notation is [implemented as a PEG grammar](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini/krill.pegjs), living in the [mini package](https://codeberg.org/uzu/strudel/src/branch/main/packages/mini)
|
|
||||||
- it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn
|
|
||||||
- the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/)
|
|
||||||
- the generated parser takes a mini notation string and outputs an AST
|
|
||||||
- the AST can then be used to construct a pattern using the regular Strudel API
|
|
||||||
|
|
||||||
Here's an example AST:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type_": "pattern",
|
|
||||||
"arguments_": { "alignment": "h" },
|
|
||||||
"source_": [
|
|
||||||
{
|
|
||||||
"type_": "element", "source_": "c3",
|
|
||||||
"location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type_": "element",
|
|
||||||
"location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } }
|
|
||||||
"source_": {
|
|
||||||
"type_": "pattern", "arguments_": { "alignment": "h" },
|
|
||||||
"source_": [
|
|
||||||
{
|
|
||||||
"type_": "element", "source_": "e3",
|
|
||||||
"location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type_": "element", "source_": "g3",
|
|
||||||
"location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
which translates to `seq(c3, seq(e3, g3))`
|
|
||||||
|
|
||||||
## 2. Querying & Scheduling
|
|
||||||
|
|
||||||
When the user code has been evaluated, we hopefully get a Pattern instance, which we can use to query events from.
|
|
||||||
These events can then be used to trigger side effects in the real world. On that note, Events are mostly called Hap(s) in the codebase, because JS already has a built in `Event` class.
|
|
||||||
|
|
||||||
### Querying
|
|
||||||
|
|
||||||
> Querying = Asking a Pattern for Events within a certain time span
|
|
||||||
|
|
||||||
```js
|
|
||||||
seq('c3', ['e3', 'g3']) // <--- Pattern
|
|
||||||
.queryArc(0, 2) // query events within 0 and 2 cycles
|
|
||||||
.map((hap) => hap.showWhole()); // make readable
|
|
||||||
```
|
|
||||||
|
|
||||||
yields
|
|
||||||
|
|
||||||
```js
|
|
||||||
[
|
|
||||||
'0/1 -> 1/2: c3', // cycle 0
|
|
||||||
'1/2 -> 3/4: e3',
|
|
||||||
'3/4 -> 1/1: g3',
|
|
||||||
'1/1 -> 3/2: c3', // cycle 1
|
|
||||||
'3/2 -> 7/4: e3',
|
|
||||||
'7/4 -> 2/1: g3',
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🗓️ Scheduling
|
|
||||||
|
|
||||||
The scheduler will query events repeatedly, creating a possibly endless loop of time slices.
|
|
||||||
Here is a simplified example of how it works
|
|
||||||
|
|
||||||
```js
|
|
||||||
let step = 0.5; // query interval in seconds
|
|
||||||
let tick = 0; // how many intervals have passed
|
|
||||||
let pattern = seq('c3', ['e3', 'g3']); // pattern from user
|
|
||||||
setInterval(() => {
|
|
||||||
const events = pattern.queryArc(tick * step, ++tick * step);
|
|
||||||
events.forEach((event) => {
|
|
||||||
console.log(event.showWhole());
|
|
||||||
const o = getAudioContext().createOscillator();
|
|
||||||
o.frequency.value = getFreq(event.value);
|
|
||||||
o.start(event.whole.begin);
|
|
||||||
o.stop(event.whole.begin + event.duration);
|
|
||||||
o.connect(getAudioContext().destination);
|
|
||||||
});
|
|
||||||
}, step * 1000); // query each "step" seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. Sound Output
|
|
||||||
|
|
||||||
The third and last step is to use the scheduled events to make sound.
|
|
||||||
Patterns are wrapped with param functions to compose different properties of the sound.
|
|
||||||
|
|
||||||
```js
|
|
||||||
note("[c2(3,8) [<eb2 g1> bb1]]") // sets frequency
|
|
||||||
.s("<sawtooth square>") // sound source
|
|
||||||
.gain(.5) // turn down volume
|
|
||||||
.cutoff(sine.range(200,1000).slow(4)) // modulated cutoff
|
|
||||||
.slow(2)
|
|
||||||
.out().logValues()`,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is an example Hap value with different properties:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{ note: 'a4', s: 'sawtooth', gain: 0.5, cutoff: 267 }
|
|
||||||
```
|
|
||||||
<img src="https://codeberg.org/uzu/strudel/raw/branch/talk/talk/public/waa-nodes.png" width="600" />
|
|
||||||
|
|
||||||
<div className="text-left">
|
|
||||||
|
|
||||||
- Patterns represent just values in time!
|
|
||||||
- Suitable for any time based output (music, visuals, movement, .. ?)
|
|
||||||
|
|
||||||
### Supported Outputs
|
|
||||||
|
|
||||||
At the time of writing this doc, the following outputs are supported:
|
|
||||||
|
|
||||||
- Web Audio API `.out()` see [/webaudio](https://codeberg.org/uzu/strudel/src/branch/main/packages/webaudio)
|
|
||||||
- MIDI `.midi()` see [/midi](https://codeberg.org/uzu/strudel/src/branch/main/packages/midi)
|
|
||||||
- OSC `.osc()` see [/osc](https://codeberg.org/uzu/strudel/src/branch/main/packages/osc)
|
|
||||||
- Serial `.serial()` see [/serial](https://codeberg.org/uzu/strudel/src/branch/main/packages/serial)
|
|
||||||
- Tone.js `.tone()` (deprecated?) [/tone](https://codeberg.org/uzu/strudel/src/branch/main/packages/tone)
|
|
||||||
- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://codeberg.org/uzu/strudel/src/branch/main/packages/webdirt)
|
|
||||||
- Speech `.speak()` (experimental) part of [/core](https://codeberg.org/uzu/strudel/src/branch/main/packages/core)
|
|
||||||
|
|
||||||
These could change, so make sure to check the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages).
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
---
|
|
||||||
// fetch all commits for just this page's path
|
|
||||||
type Props = {
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
const { path } = Astro.props as Props;
|
|
||||||
const resolvedPath = `website/src/pages${path}.mdx`;
|
|
||||||
const url = `https://api.github.com/repos/tidalcycles/strudel/commits?path=${resolvedPath}`;
|
|
||||||
const commitsURL = `https://github.com/tidalcycles/strudel/commits/main/${resolvedPath}`;
|
|
||||||
|
|
||||||
type Commit = {
|
|
||||||
author: {
|
|
||||||
id: string;
|
|
||||||
login: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getCommits(url: string) {
|
|
||||||
try {
|
|
||||||
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello';
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`;
|
|
||||||
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: auth,
|
|
||||||
'User-Agent': 'astro-docs/1.0',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(
|
|
||||||
`Request to fetch commits failed. Reason: ${res.statusText}
|
|
||||||
Message: ${data.message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data as Commit[];
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`[error] /src/components/AvatarList.astro
|
|
||||||
${(e as any)?.message ?? e}`);
|
|
||||||
return [] as Commit[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeDups(arr: Commit[]) {
|
|
||||||
const map = new Map<string, Commit['author']>();
|
|
||||||
for (let item of arr) {
|
|
||||||
const author = item.author;
|
|
||||||
// Deduplicate based on author.id
|
|
||||||
//map.set(author.id, { login: author.login, id: author.id });
|
|
||||||
author && map.set(author.id, { login: author.login, id: author.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...map.values()];
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await getCommits(url);
|
|
||||||
const unique = removeDups(data);
|
|
||||||
const recentContributors = unique.slice(0, 3); // only show avatars for the 3 most recent contributors
|
|
||||||
const additionalContributors = unique.length - recentContributors.length; // list the rest of them as # of extra contributors
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
|
|
||||||
<div class="contributors px-4 mb-8">
|
|
||||||
<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}>
|
|
||||||
{
|
|
||||||
recentContributors.map((item) => (
|
|
||||||
<li>
|
|
||||||
<a href={`https://github.com/${item.login}`}>
|
|
||||||
<img
|
|
||||||
alt={`Contributor ${item.login}`}
|
|
||||||
title={`Contributor ${item.login}`}
|
|
||||||
width="64"
|
|
||||||
height="64"
|
|
||||||
src={`https://avatars.githubusercontent.com/u/${item.id}`}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
{
|
|
||||||
additionalContributors > 0 && (
|
|
||||||
<span>
|
|
||||||
<a href={commitsURL}>{`and ${additionalContributors} additional contributor${
|
|
||||||
additionalContributors > 1 ? 's' : ''
|
|
||||||
}.`}</a>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{unique.length === 0 && <a href={commitsURL}>Contributors</a>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.avatar-list {
|
|
||||||
--avatar-size: 2.5rem;
|
|
||||||
--avatar-count: 3;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
list-style: none;
|
|
||||||
/* Default to displaying most of the avatar to
|
|
||||||
enable easier access on touch devices, ensuring
|
|
||||||
the WCAG touch target size is met or exceeded */
|
|
||||||
grid-template-columns: repeat(var(--avatar-count), max(44px, calc(var(--avatar-size) / 1.15)));
|
|
||||||
/* `padding` matches added visual dimensions of
|
|
||||||
the `box-shadow` to help create a more accurate
|
|
||||||
computed component size */
|
|
||||||
padding: 0.08em;
|
|
||||||
font-size: var(--avatar-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (any-hover: hover) and (any-pointer: fine) {
|
|
||||||
.avatar-list {
|
|
||||||
/* We create 1 extra cell to enable the computed
|
|
||||||
width to match the final visual width */
|
|
||||||
grid-template-columns: repeat(calc(var(--avatar-count) + 1), calc(var(--avatar-size) / 1.75));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-list li {
|
|
||||||
width: var(--avatar-size);
|
|
||||||
height: var(--avatar-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-list li:hover ~ li a,
|
|
||||||
.avatar-list li:focus-within ~ li a {
|
|
||||||
transform: translateX(33%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-list img,
|
|
||||||
.avatar-list a {
|
|
||||||
display: block;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-list a {
|
|
||||||
transition: transform 180ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-list img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-list a:focus {
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
/* Double-layer trick to work for dark and light backgrounds */
|
|
||||||
box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contributors {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contributors > * + * {
|
|
||||||
margin-left: 0.75rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -82,7 +82,7 @@ export default function Search() {
|
||||||
appId={ALGOLIA.appId}
|
appId={ALGOLIA.appId}
|
||||||
apiKey={ALGOLIA.apiKey}
|
apiKey={ALGOLIA.apiKey}
|
||||||
getMissingResultsUrl={({ query }) => {
|
getMissingResultsUrl={({ query }) => {
|
||||||
return `https://github.com/tidalcycles/strudel/issues/new?title=Missing doc for ${query}`;
|
return `https://codeberg.org/uzu/strudel/issues/new?title=Missing%20doc%20for${encodeURIComponent(query)}`;
|
||||||
}}
|
}}
|
||||||
transformItems={(items) => {
|
transformItems={(items) => {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,4 @@ currentPage = currentPage.endsWith('/') ? currentPage.slice(0, -1) : currentPage
|
||||||
<nav aria-labelledby="grid-right" class="w-64 text-foreground">
|
<nav aria-labelledby="grid-right" class="w-64 text-foreground">
|
||||||
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
|
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
|
||||||
<MoreMenu editHref={githubEditUrl} />
|
<MoreMenu editHref={githubEditUrl} />
|
||||||
<!-- <AvatarList path={currentPage} /> -->
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ These pages will introduce you to [Strudel](https://strudel.cc/), a web-based [l
|
||||||
|
|
||||||
# What is Strudel?
|
# What is Strudel?
|
||||||
|
|
||||||
[Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://github.com/felixroos) in 2022.
|
[Strudel](https://strudel.cc/) is a version of [Tidal Cycles](https://tidalcycles.org) written in [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), initiated by [Alex McLean](https://slab.org) and [Felix Roos](https://froos.cc/) in 2022.
|
||||||
Tidal Cycles, also known as Tidal, is a language for [algorithmic pattern](https://algorithmicpattern.org), and though it is most commonly used for [making music](https://tidalcycles.org/docs/showcase), it can be used for any kind of pattern making activity, including [weaving](https://www.youtube.com/watch?v=TfEmEsusXjU).
|
Tidal Cycles, also known as Tidal, is a language for [algorithmic pattern](https://algorithmicpattern.org), and though it is most commonly used for [making music](https://tidalcycles.org/docs/showcase), it can be used for any kind of pattern making activity, including [weaving](https://www.youtube.com/watch?v=TfEmEsusXjU).
|
||||||
|
|
||||||
Tidal was first implemented as a library written in the [Haskell](https://www.haskell.org/) functional programming language, and by itself it does not make any sound.
|
Tidal was first implemented as a library written in the [Haskell](https://www.haskell.org/) functional programming language, and by itself it does not make any sound.
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export function WelcomeTab({ context }) {
|
||||||
GNU Affero General Public License
|
GNU Affero General Public License
|
||||||
</a>
|
</a>
|
||||||
. You can find the source code at{' '}
|
. You can find the source code at{' '}
|
||||||
<a href="https://github.com/tidalcycles/strudel" target="_blank">
|
<a href="https://codeberg.org/uzu/strudel" target="_blank">
|
||||||
github
|
github
|
||||||
</a>
|
</a>
|
||||||
. You can also find <a href="https://github.com/felixroos/dough-samples/blob/main/README.md">licensing info</a>{' '}
|
. You can also find <a href="https://github.com/felixroos/dough-samples/blob/main/README.md">licensing info</a>{' '}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue