Merge branch 'main' into uzu

This commit is contained in:
Felix Roos
2025-06-18 21:26:51 +02:00
186 changed files with 3188 additions and 2138 deletions
+37
View File
@@ -0,0 +1,37 @@
name: Build and Deploy
on: [workflow_dispatch]
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: docker
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: 20
# cache: "pnpm"
- name: Install Dependencies
run: pnpm install
- name: Build
run: pnpm build
- name: Deploy
run: |
eval $(ssh-agent -s)
echo "$SSH_PRIVATE_KEY" | ssh-add -
apt update && apt install -y rsync
mkdir ~/.ssh
ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts
rsync -atv --delete --delete-after --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy
@@ -4,7 +4,7 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
runs-on: docker
strategy:
matrix:
node-version: [20]
@@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
# cache: 'pnpm'
- run: pnpm install
- run: pnpm run format-check
- run: pnpm run lint
-49
View File
@@ -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@v4
- uses: pnpm/action-setup@v4
with:
version: 9.12.2
- uses: actions/setup-node@v4
with:
node-version: 20
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@v3
with:
# Upload entire repository
path: "./website/dist"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+1
View File
@@ -130,3 +130,4 @@ fabric.properties
samples/*
!samples/README.md
.idea/
+1
View File
@@ -13,3 +13,4 @@ pnpm-workspace.yaml
website/.astro
!tidal-drum-machines.json
!tidal-drum-machines-alias.json
.pnpm-store
+15 -5
View File
@@ -2,11 +2,21 @@
Thanks for wanting to contribute!!! There are many ways you can add value to this project
## Move to codeberg
We are currently in the process of moving from github to codeberg -- not everything is working, please bear with us.
To update your local clone, you can run this command:
```
git remote set-url origin git@codeberg.org:uzu/strudel.git
```
## Communication Channels
To get in touch with the contributors, either
- open a [github discussion](https://github.com/tidalcycles/strudel/discussions) or
- [join the Tidal Discord Channel](https://discord.gg/remJ6gQA) and go to the #strudel channel
- Find related discussions on the [tidal club forum](https://club.tidalcycles.org/)
@@ -32,7 +42,7 @@ Use one of the Communication Channels listed above.
## Improve the Docs
If you find some weak spots in the [docs](https://strudel.cc/workshop/getting-started/),
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
you can edit each file directly on codeburg. (we are currently fixing the "Edit this page" links in the right sidebar)
## Propose a Feature
@@ -41,7 +51,7 @@ Maybe you even want to help with the implementation of that feature!
## Report a Bug
If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://github.com/tidalcycles/strudel/issues).
If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://codeberg.org/uzu/strudel/issues).
Please check that it has not been reported before.
## Fix a Bug
@@ -71,7 +81,7 @@ To get the project up and running for development, make sure you have installed:
then, do the following:
```sh
git clone https://github.com/tidalcycles/strudel.git && cd strudel
git clone https://codeberg.org/uzu/strudel.git && cd strudel
pnpm i # install at root to symlink packages
pnpm start # start repl
```
@@ -113,7 +123,7 @@ 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.
The project is split into multiple [packages](https://codeberg.org/uzu/strudel/src/branch/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/<package-name>` to get the local version,
allowing to develop multiple packages at the same time.
+4 -3
View File
@@ -1,8 +1,9 @@
# strudel
[![Strudel test status](https://github.com/tidalcycles/strudel/actions/workflows/test.yml/badge.svg)](https://github.com/tidalcycles/strudel/actions) [![DOI](https://zenodo.org/badge/450927247.svg)](https://doi.org/10.5281/zenodo.6659278)
Live coding patterns on the web
https://strudel.cc/
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is a bit more stable now, but please continue to tread carefully.
Development is moving to https://codeberg.org/uzu/strudel
- Try it here: <https://strudel.cc>
- Docs: <https://strudel.cc/learn>
@@ -39,7 +40,7 @@ Licensing info for the default sound banks can be found over on the [dough-sampl
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
<a href="https://github.com/tidalcycles/strudel/graphs/contributors">
<a href="https://codeberg.org/uzu/strudel/activity/contributors">
<img src="https://contrib.rocks/image?repo=tidalcycles/strudel" />
</a>
@@ -201,7 +201,7 @@ interfaces.
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
<https://codeberg.org/uzu/strudel/releases>, all the code is open source
under the GPL-3.0 License.
# Acknowledgments
@@ -128,7 +128,7 @@ For the future, it is planned to integrate alternative sound engines such as Gli
# Links
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
+954
View File
@@ -0,0 +1,954 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<meta name="date" content="2022-12-14" />
<title>Strudel: live coding patterns on the Web</title>
<style type="text/css">
code {
white-space: pre;
}
</style>
<style type="text/css">
pre > code.sourceCode {
white-space: pre;
position: relative;
}
pre > code.sourceCode > span {
display: inline-block;
line-height: 1.25;
}
pre > code.sourceCode > span:empty {
height: 1.2em;
}
.sourceCode {
overflow: visible;
}
code.sourceCode > span {
color: inherit;
text-decoration: inherit;
}
div.sourceCode {
margin: 1em 0;
}
pre.sourceCode {
margin: 0;
}
@media screen {
div.sourceCode {
overflow: auto;
}
}
@media print {
pre > code.sourceCode {
white-space: pre-wrap;
}
pre > code.sourceCode > span {
text-indent: -5em;
padding-left: 5em;
}
}
pre.numberSource code {
counter-reset: source-line 0;
}
pre.numberSource code > span {
position: relative;
left: -4em;
counter-increment: source-line;
}
pre.numberSource code > span > a:first-child::before {
content: counter(source-line);
position: relative;
left: -1em;
text-align: right;
vertical-align: baseline;
border: none;
display: inline-block;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
padding: 0 4px;
width: 4em;
color: #aaaaaa;
}
pre.numberSource {
margin-left: 3em;
border-left: 1px solid #aaaaaa;
padding-left: 4px;
}
div.sourceCode {
}
@media screen {
pre > code.sourceCode > span > a:first-child::before {
text-decoration: underline;
}
}
code span.al {
color: #ff0000;
font-weight: bold;
} /* Alert */
code span.an {
color: #60a0b0;
font-weight: bold;
font-style: italic;
} /* Annotation */
code span.at {
color: #7d9029;
} /* Attribute */
code span.bn {
color: #40a070;
} /* BaseN */
code span.bu {
color: #008000;
} /* BuiltIn */
code span.cf {
color: #007020;
font-weight: bold;
} /* ControlFlow */
code span.ch {
color: #4070a0;
} /* Char */
code span.cn {
color: #880000;
} /* Constant */
code span.co {
color: #60a0b0;
font-style: italic;
} /* Comment */
code span.cv {
color: #60a0b0;
font-weight: bold;
font-style: italic;
} /* CommentVar */
code span.do {
color: #ba2121;
font-style: italic;
} /* Documentation */
code span.dt {
color: #902000;
} /* DataType */
code span.dv {
color: #40a070;
} /* DecVal */
code span.er {
color: #ff0000;
font-weight: bold;
} /* Error */
code span.ex {
} /* Extension */
code span.fl {
color: #40a070;
} /* Float */
code span.fu {
color: #06287e;
} /* Function */
code span.im {
color: #008000;
font-weight: bold;
} /* Import */
code span.in {
color: #60a0b0;
font-weight: bold;
font-style: italic;
} /* Information */
code span.kw {
color: #007020;
font-weight: bold;
} /* Keyword */
code span.op {
color: #666666;
} /* Operator */
code span.ot {
color: #007020;
} /* Other */
code span.pp {
color: #bc7a00;
} /* Preprocessor */
code span.sc {
color: #4070a0;
} /* SpecialChar */
code span.ss {
color: #bb6688;
} /* SpecialString */
code span.st {
color: #4070a0;
} /* String */
code span.va {
color: #19177c;
} /* Variable */
code span.vs {
color: #4070a0;
} /* VerbatimString */
code span.wa {
color: #60a0b0;
font-weight: bold;
font-style: italic;
} /* Warning */
</style>
<link rel="stylesheet" href="css/iclc.css" />
</head>
<body>
<div id="header">
<h1 class="title">Strudel: live coding patterns on the Web</h1>
<ul id="authorlist">
<li>true</li>
<li>true</li>
</ul>
<h3 class="date">2022-12-14</h3>
</div>
<h2 class="abstract">Abstract</h2>
<div id="abstract">
<p>
This paper introduces Strudel, which brings the TidalCycles approach to live coding algorithmic patterns to
native JavaScript and the web. We begin by giving a little background of the first year of development, before
sharing some detail about its implementation and examples of use. We go on to outline the wide range of
synthesis and other outputs available in Strudel, including WebAudio, MIDI, OSC (for SuperDirt), WebSerial and
CSound, and introduce Strudels REPL live editor, including its built-in visualisations. We then compare Strudel
with Tidal, the trade-offs involved between JavaScript and Haskell, and the unique capabilities offered by
Strudel for aligning patterns.
</p>
</div>
<h1 data-number="1" id="introduction"><span class="header-section-number">1</span> Introduction</h1>
<p>
In the following paper, we introduce <em>Strudel</em>, an alternative implementation of the TidalCycles (or
Tidal for short) live coding system, using the JavaScript programming language. Strudel is an attempt to make
live coding more accessible, by creating a system that runs entirely in the browser, while opening Tidals
approach to algorithmic patterns
<span class="citation" data-cites="mcleanAlgorithmicPattern2020a">(Mclean 2020)</span> up to modern audio/visual
web technologies. The Strudel REPL is a live code editor dedicated to manipulating patterns while they play, with
builtin visual feedback. While Strudel is written in JavaScript, the API is optimized for simplicity and
readability by applying code transformations on the syntax tree level, allowing language operations that would
otherwise be impossible. The application supports multiple ways to output sound, including Tone.js, Web Audio
Nodes, OSC (Open Sound Control) messages, Web Serial, Web MIDI and Csound. The project is split into multiple
packages, allowing granular reuse in other applications. Apart from TidalCycles, Strudel draws inspiration from
many prior existing projects like TidalVortex
<span class="citation" data-cites="mcleanTidalVortexZero2022">(McLean et al. 2022)</span>, Gibber
<span class="citation" data-cites="robertsGibberLiveCoding2012">(Roberts and Kuchera-morin 2012)</span>, Estuary
<span class="citation" data-cites="ogbornEstuaryBrowserbasedCollaborative2017">(Ogborn et al. 2017)</span>, Hydra
<span class="citation" data-cites="jackHydra2022">(Jack [2022] 2022)</span>, Ocarina
<span class="citation" data-cites="solomonPurescriptocarina2022">(Solomon [2021] 2022)</span> and Feedforward
<span class="citation" data-cites="mcleanFeedforward2020">(McLean 2020)</span>. This paper expands the Strudel
Demo paper for the Web Audio Conference 2022
<span class="citation" data-cites="StrudelWAC2022">(Roos and McLean 2022)</span>.
</p>
<p>
The first tentative commit to the Strudel project was on 22nd January 2022 by Alex McLean, with the core
representation implemented over the following few days. Although this was his first attempt at a JavaScript-based
application, by 27th January, Alex had managed to upload the initial version to the npm javascript package
database, sharing with the wider community for comment. By 4th February, Felix Roos had discovered Strudel and
contributed a REPL user interface to it, and then contributed a scheduler the next day, so that Strudel could
already make sound. At this point, Alex and Felix shared ownership to the repository, and the project has since
proved to be a productive confluence of Felixs own work into music representation and visualisation, with Alexs
experience with making Tidal. Felix has since become the primary contributor to Strudel, with Alex continuing to
jump between developing both Strudel and Tidal. Aspects of Strudels development have therefore fed back into
TidalCycles, and both systems have maintained a shared conceptual underpinning. We plan to continue working
towards feature parity between these systems, although within the syntactical trade-offs and library ecosystems of
JavaScript and Haskell, some divergence is inevitable and healthy.
</p>
<p>
Over the first year of its life, Strudel is now a fully-fledged live coding environment, porting Tidals core
represention of patterns, pattern transformations, and mininotation for polymetric sequences, combined with a
wealth of features for synthesising and visualising those patterns.
</p>
<h1 data-number="2" id="from-tidal-to-strudel-and-back">
<span class="header-section-number">2</span> From Tidal to Strudel and back
</h1>
<p>
As mentioned above, the original Tidal is implemented as a domain specific language (DSL) embedded in the Haskell
pure functional programming language, and takes advantage of Haskells terse syntax and advanced, strong type
system. JavaScript on the other hand, is a multi-paradigm programming language, with a dynamic type system.
Because Tidal leans heavily on many of Haskells more unique features, it was not always clear that it could
meaningfully be ported to a multi-paradigm scripting language. However, this possibility was already demonstrated
with an earlier port to Python [TidalVortex;
<span class="citation" data-cites="mcleanTidalVortexZero2022">McLean et al. (2022)</span>], and we have now
successfully implemented Tidals pure functional representation of patterns in Strudel, including partial
application, currying, and the functor, applicative and monadic structures that underlie Tidals expressive
pattern transformations. The result is a terse and highly composable system, where everything is either a pattern,
or a function for combining and manipulating patterns, offering a rich creative ground for exploration.
</p>
<p>
This development process has been far from a one-way port, however. The process of porting Tidals concepts has
also opened up new possibilities, some just from revisiting every design decision, and some from the particular
affordances and constraints offered by JavaScript. This has lead to new features (and indeed bugfixes) that have
found their way back to Tidal where appropriate, and ongoing work that we will return to in the conclusion of this
paper.
</p>
<h1 data-number="3" id="representing-patterns">
<span class="header-section-number">3</span> Representing Patterns
</h1>
<p>
Patterns are the essence of Tidal. Its patterns are abstract entities that represent flows of time as functions,
adapting a technique called pure functional reactive programming. Taking a time span as its input, a Pattern can
output a set of events that happen within that time span. It depends on the structure of the Pattern how the
events are located in time. From now on, this process of generating events from a time span will be called
<strong>querying</strong>. Example:
</p>
<div class="sourceCode" id="cb1">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> pattern <span class="op">=</span> <span class="fu">sequence</span>(c3<span class="op">,</span> [e3<span class="op">,</span> g3])</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> events <span class="op">=</span> pattern<span class="op">.</span><span class="fu">queryArc</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">1</span>)</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(events<span class="op">.</span><span class="fu">map</span>(e <span class="kw">=&gt;</span> e<span class="op">.</span><span class="fu">show</span>()))</span></code></pre>
</div>
<p>
In this example, we create a pattern using the <code>sequence</code> function and <strong>query</strong> it for
the time span from <code>0</code> to <code>1</code>. Those numbers represent units of time called
<strong>cycles</strong>. The length of one cycle depends on the tempo, which defaults to one cycle per second. The
resulting events are:
</p>
<div class="sourceCode" id="cb2">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>[{ <span class="dt">value</span><span class="op">:</span> <span class="st">&#39;c3&#39;</span><span class="op">,</span> <span class="dt">begin</span><span class="op">:</span> <span class="dv">0</span><span class="op">,</span> <span class="dt">end</span><span class="op">:</span> <span class="dv">1</span><span class="op">/</span><span class="dv">2</span> }<span class="op">,</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>{ <span class="dt">value</span><span class="op">:</span> <span class="st">&#39;e3&#39;</span><span class="op">,</span> <span class="dt">begin</span><span class="op">:</span> <span class="dv">1</span><span class="op">/</span><span class="dv">2</span><span class="op">,</span> <span class="dt">end</span><span class="op">:</span> <span class="dv">3</span><span class="op">/</span><span class="dv">4</span> }<span class="op">,</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>{ <span class="dt">value</span><span class="op">:</span> <span class="st">&#39;g3&#39;</span><span class="op">,</span> <span class="dt">begin</span><span class="op">:</span> <span class="dv">3</span><span class="op">/</span><span class="dv">4</span><span class="op">,</span> <span class="dt">end</span><span class="op">:</span> <span class="dv">1</span> }]</span></code></pre>
</div>
<p>
Each event has a value, a begin time and an end time, where time is represented as a fraction. In the above case,
the events are placed in sequential order, where c3 takes the first half, and e3 and g3 together take the second
half. This temporal placement is the result of the <code>sequence</code> function, which divides its arguments
equally over one cycle. If an argument is an array, the same rule applies to that part of the cycle. In the
example, e3 and g3 are divided equally over the second half of the whole cycle.
</p>
<p>
The above examples do not represent how Strudel is used in practice. In the live coding editor, the user only has
to type in the pattern itself, the querying will be handled by the scheduler. The scheduler will repeatedly query
the pattern for events, which are then scheduled as sound synthesis or other event triggers. Also, the above event
data structure has been simplified for readability.
</p>
<figure>
<img
src="images/strudel-screenshot2.png"
style="width: 60%"
alt="Screenshot of the Strudel REPL live coding editor, including piano-roll visualisation."
/>
<figcaption aria-hidden="true">
Screenshot of the Strudel REPL live coding editor, including piano-roll visualisation.
</figcaption>
</figure>
<h1 data-number="4" id="making-patterns"><span class="header-section-number">4</span> Making Patterns</h1>
<p>
In practice, the end-user live coder will not deal with constructing patterns directly, but will rather build
patterns using Strudels extensive combinator library to create, combine and transform patterns.
</p>
<p>
The live coder will rarely use the <code>sequence</code> function as seen above, as sequencing is implicit in many
functions. For example in the following, the <code>note</code> function constructs a pattern of notes, sequencing
its arguments in the same manner as the previous example.
</p>
<div class="sourceCode" id="cb3">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">note</span>(c3<span class="op">,</span> [e3<span class="op">,</span> g3])</span></code></pre>
</div>
<p>
Perhaps more often, they will use the mini-notation for even terser notation of rhythmic sequences: [^This last
example is also valid Tidal code, albeit the parenthesis is not required in its Haskell syntax in this case. Tidal
does not support passing sequences as lists directly to the <code>note</code> function, however.].
</p>
<div class="sourceCode" id="cb4">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="fu">note</span>(<span class="st">&quot;c3 [e3 g3]&quot;</span>)</span></code></pre>
</div>
<p>
Such sequences are often treated only a starting point for manipulation, where they then undergo pattern
transformations such as repetition, symmetry, interference/combination or randomisation, potentially at multiple
timescales. Because Strudel patterns are represented as pure functions of time rather than as data structures,
very long and complex generative results can be represented and manipulated without having to store the resulting
sequences in memory.
</p>
<h1 data-number="5" id="pattern-example"><span class="header-section-number">5</span> Pattern Example</h1>
<p>
The following example showcases how patterns can be utilized to create musical complexity from simple parts, using
repetition and interference:
</p>
<div class="sourceCode" id="cb5">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;&lt;0 2 [4 6](3,4,1) 3&gt;&quot;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">off</span>(<span class="dv">1</span><span class="op">/</span><span class="dv">4</span><span class="op">,</span> <span class="fu">add</span>(<span class="dv">2</span>))</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">off</span>(<span class="dv">1</span><span class="op">/</span><span class="dv">2</span><span class="op">,</span> <span class="fu">add</span>(<span class="dv">6</span>))</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">scale</span>(<span class="st">&#39;D minor&#39;</span>)</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">legato</span>(<span class="op">.</span><span class="dv">25</span>)</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">note</span>()<span class="op">.</span><span class="fu">s</span>(<span class="st">&quot;sawtooth square&quot;</span>)</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">delay</span>(<span class="op">.</span><span class="dv">8</span>)<span class="op">.</span><span class="fu">delaytime</span>(<span class="op">.</span><span class="dv">125</span>)</span></code></pre>
</div>
<p>
The pattern starts with a rhythm of numbers in mini notation, which are later interpreted inside the scale of D
minor. The first line could also be expressed without mini notation:
</p>
<div class="sourceCode" id="cb6">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="fu">cat</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">2</span><span class="op">,</span> [<span class="dv">4</span><span class="op">,</span> <span class="dv">6</span>]<span class="op">.</span><span class="fu">euclid</span>(<span class="dv">3</span><span class="op">,</span> <span class="dv">4</span><span class="op">,</span> <span class="dv">1</span>)<span class="op">,</span> <span class="dv">3</span>)</span></code></pre>
</div>
<p>
These numbers then undergo various pattern transformations. Here is a short description of all the functions used:
</p>
<ul>
<li><code>cat</code>: play elements sequentially, where each lasts one cycle</li>
<li><code>brackets</code>: elements inside brackets are divided equally over the time of their parent</li>
<li>
<code>.euclid(p, s, o)</code>: place p pulses evenly over s steps, with offset o
<span class="citation" data-cites="toussaintEuclideanAlgorithmGenerates2005">(Toussaint 2005)</span>
</li>
<li>
<code>.off(n, f)</code>: layers a pattern on top of itself, with the new layer offset by n cycles, and with
function f applied
</li>
<li><code>.legato(n)</code>: multiply the duration of all events in a pattern by a factor of n</li>
<li>
<code>.echo(t, n, v)</code>: copy each event t times, with n cycles in between each copy, decreasing velocity by
v
</li>
<li><code>.note()</code>: interpretes values as notes</li>
<li><code>.s(name)</code>: play back each event with the given sound</li>
<li><code>.delay(wet)</code>: add delay</li>
<li><code>.delaytime(t)</code>: set delay time</li>
</ul>
<p>Much of the above will be familiar to Tidal users.</p>
<!-- This example shows some of Strudel's unique support for chords and transposition familiar to students of Western music theory. This differs a little from Tidal's approach and thanks to the integration of the javascript library XXX (*TODO* ? or is this all your work Felix?), Strudel's support for tonal transformations such as voice leading is perhaps respects more advanced than Tidal. -->
<h1 data-number="6" id="ways-to-make-sound-and-other-events">
<span class="header-section-number">6</span> Ways to make Sound (and other events)
</h1>
<p>To generate sound, Strudel supports bindings for different outputs:</p>
<ul>
<li>Tone.js (deprecated)</li>
<li>Web Audio API</li>
<li>WebDirt, a js recreation of Tidals <em>Dirt</em> sample engine (deprecated)</li>
<li>OSC via osc-js, compatible with superdirt</li>
<li>Csound via the Csound WebAssembly build</li>
<li>MIDI via WebMIDI</li>
<li>Serial via WebSerial</li>
</ul>
<p>
At first, we used Tone.js as sound output, but it proved to be limited for the use case of Strudel, where each
individual event could potentially have a completely different audio graph. While the Web Audio API takes a
<em>fire-and-forget</em> approach, creating a lot of Tone.js instruments and effects causes performance issues
quickly. For that reason, we chose to search for alternatives.
</p>
<p>
Strudels new default output uses the Web Audio API to create a new audio graph for each event. It currently
supports basic oscillators, sample playback, various effects and an experimental support for soundfonts.
</p>
<p>
WebDirt <span class="citation" data-cites="ogbornDktr0WebDirt2022">(Ogborn [2016] 2022)</span> was created as part
of the Estuary Live Coding System
<span class="citation" data-cites="ogbornEstuaryBrowserbasedCollaborative2017">(Ogborn et al. 2017)</span>, and
proved to be a solid choice for handling samples in Strudel as well. We are however focused on working more
directly with the Web Audio API to be able to integrate new features more tightly.
</p>
<p>
Using the OSC protocol via Strudels provided Node.js-based OSC proxy server, it is possible to send network
messages to trigger events. This is mainly used to render sound using SuperDirt
<span class="citation" data-cites="SuperDirt2022">(<em>SuperDirt</em> [2015] 2022)</span>, which is the
well-developed Supercollider-based synthesis framework that Tidal live coders generally use as standard.
</p>
<p>
Recently, the experimental integration of Csound proved to bring a new dimension of sound design capabilities to
Strudel. Thanks to the WebAssembly distribution of this classic system
<span class="citation" data-cites="CsoundWebAssembly">(Yi, Lazzarini, and Costello 2018)</span>, Csound
orchestra synthesisers can be embedded in and then patterned with Strudel code.
</p>
<p>
MIDI output can also be used to send MIDI messages to either external instruments or to other programs on the same
device. Unlike OSC, Strudel is able to send MIDI directly without requiring additional proxy software, but only
from web browsers that support it (at the time of writing, this means Chromium-based browsers).
</p>
<p>
Finally, Strudel supports Serial output, for example to trigger events via microcontrollers. This has already been
explored for robot choreography by Kate Sicchio and Alex McLean, via a performance presented at the International
Conference on Live Interfaces 2022.
</p>
<h1 data-number="7" id="the-strudel-repl"><span class="header-section-number">7</span> The Strudel REPL</h1>
<p>
While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the
Strudel REPL[^REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive
programming interface from computing heritage, usually for a commandline interface but also applied to live coding
editors.], which is a browser-based live coding environment. This live code editor is dedicated to manipulating
Strudel patterns while they play. The REPL features built-in visual feedback, which highlights which elements in
the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is
designed to support both learning and live use of Strudel.
</p>
<p>
Besides a UI for playback control and meta information, the main part of the REPL interface is the code editor
powered by CodeMirror. In it, the user can edit and evaluate pattern code live, using one of the available
synthesis outputs to create music and/or sound art. The control flow of the REPL follows 3 basic steps:
</p>
<ol type="1">
<li>
The user writes and updates code. Each update transpiles and evaluates it to create a
<code>Pattern</code> instance
</li>
<li>
While the REPL is running, the <code>Scheduler</code> queries the active <code>Pattern</code> by a regular
interval, generating <code>Events</code> (also known as <code>Haps</code> in Strudel) for the next time span.
</li>
<li>
For each scheduling tick, all generated <code>Events</code> are triggered by calling their
<code>onTrigger</code> method, which is set by the output.
</li>
</ol>
<figure>
<img
src="https://github.com/tidalcycles/strudel/raw/talk/talk/public/strudelflow.png?raw=true"
style="width: 43%"
alt="REPL control flow"
/>
<figcaption aria-hidden="true">REPL control flow</figcaption>
</figure>
<h2 data-number="7.1" id="user-code"><span class="header-section-number">7.1</span> User Code</h2>
<p>To create a <code>Pattern</code> from the user code, two steps are needed:</p>
<ol type="1">
<li>Transpile the JS input code to make it functional</li>
<li>Evaluate the transpiled code</li>
</ol>
<h3 data-number="7.1.1" id="transpilation-evaluation">
<span class="header-section-number">7.1.1</span> Transpilation &amp; Evaluation
</h3>
<p>
In the JavaScript world, using transpilation is a common practise to be able to use language features that are not
supported by the base language. Tools like <code>babel</code> will transpile code that contains unsupported
language features into a version of the code without those features.
</p>
<p>
In the same tradition, Strudel can add a transpilation step to simplify the user code in the context of live
coding. For example, the Strudel REPL lets the user create mini notation patterns using just double quoted
strings, while single quoted strings remain what they are:
</p>
<div class="sourceCode" id="cb7">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;c3 [e3 g3]*2&quot;</span></span></code></pre>
</div>
<p>is transpiled to:</p>
<div class="sourceCode" id="cb8">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="fu">mini</span>(<span class="st">&quot;c3 [e3 g3]*2&quot;</span>)<span class="op">.</span><span class="fu">withMiniLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">0</span><span class="op">,</span><span class="dv">0</span>]<span class="op">,</span>[<span class="dv">1</span><span class="op">,</span><span class="dv">14</span><span class="op">,</span><span class="dv">14</span>])</span></code></pre>
</div>
<p>
Here, the string is wrapped in <code>mini</code>, which will create a pattern from a mini notation string.
Additionally, the <code>withMiniLocation</code> method passes the original source code location of the string to
the pattern, which enables highlighting active events.
</p>
<p>
Other convenient features like pseudo variables, operator overloading and top level await are possible with
transpilation.
</p>
<p>After the transpilation, the code is ready to be evaluated into a <code>Pattern</code>.</p>
<p>
Behind the scenes, the user code string is parsed with <code>acorn</code>, turning it into an Abstract Syntax Tree
(AST). The AST allows changing the structure of the code before generating the transpiled version using
<code>escodegen</code>.
</p>
<h3 data-number="7.1.2" id="mini-notation"><span class="header-section-number">7.1.2</span> Mini Notation</h3>
<p>
While the transpilation allows JavaScript to express Patterns in a less verbose way, it is still preferable to use
the Mini Notation as a more compact way to express rhythm. Strudel aims to provide the same Mini Notation features
and syntax as used in Tidal.
</p>
<p>
The Mini Notation parser is implemented using <code>peggy</code>, which allows generating performant parsers for
Domain Specific Languages (DSLs) using a concise grammar notation. The generated parser turns the Mini Notation
string into an AST which is used to call the respective Strudel functions with the given structure. For example,
<code>"c3 [e3 g3]*2"</code> will result in the following calls:
</p>
<div class="sourceCode" id="cb9">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="fu">seq</span>(</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">reify</span>(<span class="st">&#39;c3&#39;</span>)<span class="op">.</span><span class="fu">withLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">1</span><span class="op">,</span><span class="dv">1</span>]<span class="op">,</span> [<span class="dv">1</span><span class="op">,</span><span class="dv">4</span><span class="op">,</span><span class="dv">4</span>])<span class="op">,</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">seq</span>(</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">reify</span>(<span class="st">&#39;e3&#39;</span>)<span class="op">.</span><span class="fu">withLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">5</span><span class="op">,</span><span class="dv">5</span>]<span class="op">,</span> [<span class="dv">1</span><span class="op">,</span><span class="dv">8</span><span class="op">,</span><span class="dv">8</span>])<span class="op">,</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">reify</span>(<span class="st">&#39;g3&#39;</span>)<span class="op">.</span><span class="fu">withLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">8</span><span class="op">,</span><span class="dv">8</span>]<span class="op">,</span> [<span class="dv">1</span><span class="op">,</span><span class="dv">10</span><span class="op">,</span><span class="dv">10</span>])<span class="op">,</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a> )<span class="op">.</span><span class="fu">fast</span>(<span class="dv">2</span>)</span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>)</span></code></pre>
</div>
<h3 data-number="7.1.3" id="highlighting-locations">
<span class="header-section-number">7.1.3</span> Highlighting Locations
</h3>
<p>
As seen in the examples above, both the JS and the Mini Notation parser add source code locations using
<code>withMiniLocation</code> and <code>withLocation</code> methods. While the JS parser adds locations relative
to the user code as a whole, the Mini Notation adds locations relative to the position of the mini notation
string. The absolute location of elements within Mini Notation can be calculated by simply adding both locations
together. This absolute location can be used to highlight active events in real time.
</p>
<h2 data-number="7.2" id="scheduling-events"><span class="header-section-number">7.2</span> Scheduling Events</h2>
<p>
After an instance of <code>Pattern</code> is obtained from the user code, it is used by the scheduler to get
queried for events. Once started, the scheduler runs at a fixed interval to query active pattern for events
withing the current intervals time span. A simplified implementation looks like this:
</p>
<div class="sourceCode" id="cb10">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> pattern <span class="op">=</span> <span class="fu">seq</span>(<span class="st">&#39;c3&#39;</span><span class="op">,</span> [<span class="st">&#39;e3&#39;</span><span class="op">,</span> <span class="st">&#39;g3&#39;</span>])<span class="op">;</span> <span class="co">// pattern from user</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> interval <span class="op">=</span> <span class="fl">0.5</span><span class="op">;</span> <span class="co">// query interval in seconds</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> time <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> <span class="co">// beginning of current time span</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> minLatency <span class="op">=</span> <span class="op">.</span><span class="dv">1</span><span class="op">;</span> <span class="co">// min time before a hap should trigger</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="pp">setInterval</span>(() <span class="kw">=&gt;</span> {</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> haps <span class="op">=</span> pattern<span class="op">.</span><span class="fu">queryArc</span>(time<span class="op">,</span> time <span class="op">+</span> interval)<span class="op">;</span></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> time <span class="op">+=</span> interval<span class="op">;</span> <span class="co">// increment time</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> haps<span class="op">.</span><span class="fu">forEach</span>((hap) <span class="kw">=&gt;</span> {</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> deadline <span class="op">=</span> hap<span class="op">.</span><span class="at">whole</span><span class="op">.</span><span class="at">begin</span> <span class="op">-</span> time <span class="op">+</span> minLatency<span class="op">;</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a> <span class="fu">onTrigger</span>(hap<span class="op">,</span> deadline<span class="op">,</span> duration)<span class="op">;</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a> })<span class="op">;</span></span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a>}<span class="op">,</span> interval <span class="op">*</span> <span class="dv">1000</span>)<span class="op">;</span> <span class="co">// query each &quot;interval&quot; seconds</span></span></code></pre>
</div>
<p>
Note that the above code is simplified for illustrative purposes. The actual implementation has to work around
imprecise callbacks of <code>setInterval</code>. More about the implementation details can be read in
<a href="https://loophole-letters.vercel.app/web-audio-scheduling">this blog post</a>.
</p>
<p>
The fact that <code>Pattern.queryArc</code> is a pure function that maps a time span to a set of events allows us
to choose any interval we like without changing the resulting output. It also means that when the pattern is
changed from outside, the next scheduling callback will work with the new pattern, keeping its clock running.
</p>
<p>
The latency between the time the pattern is evaluated and the change is heard is between
<code>minLatency</code> and <code>interval + minLatency</code>, in our example between 100ms and 600ms. In
Strudel, the current query interval is 50ms with a minLatency of 100ms, meaning the latency is between 50ms and
150ms.
</p>
<h2 data-number="7.3" id="output"><span class="header-section-number">7.3</span> Output</h2>
<p>
The last step is to trigger each event in the chosen output. This is where the given time and value of each event
is used to generate audio or any other form of time based output. The default output of the Strudel REPL is the
WebAudio output. To understand what an output does, we first have to understand what control parameters are.
</p>
<h3 data-number="7.3.1" id="control-parameters">
<span class="header-section-number">7.3.1</span> Control Parameters
</h3>
<p>
To be able to manipulate multiple aspects of sound in parallel, so called control parameters are used to shape the
value of each event. Example:
</p>
<div class="sourceCode" id="cb11">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="fu">note</span>(<span class="st">&quot;c3 e3&quot;</span>)<span class="op">.</span><span class="fu">cutoff</span>(<span class="dv">1000</span>)<span class="op">.</span><span class="fu">s</span>(<span class="st">&#39;sawtooth&#39;</span>)</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">queryArc</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">1</span>)<span class="op">.</span><span class="fu">map</span>(hap <span class="kw">=&gt;</span> hap<span class="op">.</span><span class="at">value</span>)</span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="co">/* [</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="co"> { note: &#39;c3&#39;, cutoff: 1000, s: &#39;sawtooth&#39; }</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="co"> { note: &#39;e3&#39;, cutoff: 1000, s: &#39;sawtooth&#39; }</span></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="co">] */</span></span></code></pre>
</div>
<p>
Here, the control parameter functions <code>note</code>, <code>cutoff</code> and <code>s</code> are used, where
each controls a different property in the value object. Each control parameter function accepts a primitive value,
a list of values to be sequenced into a <code>Pattern</code>, or a <code>Pattern</code>. In the example,
<code>note</code> gets a <code>Pattern</code> from a Mini Notation expression (double quoted), while
<code>cutoff</code> and <code>s</code> are given a <code>Number</code> and a (single quoted)
<code>String</code> respectively.
</p>
<p>
Strudel comes with a large default set of control parameter functions that are based on the ones used by Tidal and
SuperDirt, focusing on music and audio terminology. It is however possible to create custom control paramters for
any purpose:
</p>
<div class="sourceCode" id="cb12">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> { x<span class="op">,</span> y } <span class="op">=</span> <span class="fu">createParams</span>(<span class="st">&#39;x&#39;</span><span class="op">,</span> <span class="st">&#39;y&#39;</span>)</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="fu">x</span>(sine<span class="op">.</span><span class="fu">range</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">200</span>))<span class="op">.</span><span class="fu">y</span>(cosine<span class="op">.</span><span class="fu">range</span>(<span class="dv">0</span><span class="op">,</span><span class="dv">200</span>))</span></code></pre>
</div>
<p>
This example creates the custom control parameters <code>x</code> and <code>y</code> which are then used to form a
pattern that descibes the coordinates of a circle.
</p>
<h3 data-number="7.3.2" id="outputs"><span class="header-section-number">7.3.2</span> Outputs</h3>
<p>
Now that we know how the value of an event is manipulated using control parameters, we can look at how outputs can
use that value to generate anything. The scheduler above was calling the <code>onTrigger</code> function which is
used to implement the output. A very simple version of the web audio output could look like this:
</p>
<div class="sourceCode" id="cb13">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">onTrigger</span>(hap<span class="op">,</span> deadline<span class="op">,</span> duration) {</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> { note } <span class="op">=</span> hap<span class="op">.</span><span class="at">value</span><span class="op">;</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> time <span class="op">=</span> <span class="fu">getAudioContext</span>()<span class="op">.</span><span class="at">currentTime</span> <span class="op">+</span> deadline<span class="op">;</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> o <span class="op">=</span> <span class="fu">getAudioContext</span>()<span class="op">.</span><span class="fu">createOscillator</span>()<span class="op">;</span></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="at">frequency</span><span class="op">.</span><span class="at">value</span> <span class="op">=</span> <span class="fu">getFreq</span>(note)<span class="op">;</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="fu">start</span>(time)<span class="op">;</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="fu">stop</span>(time <span class="op">+</span> <span class="bu">event</span><span class="op">.</span><span class="at">duration</span>)<span class="op">;</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="fu">connect</span>(<span class="fu">getAudioContext</span>()<span class="op">.</span><span class="at">destination</span>)<span class="op">;</span></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a>}</span></code></pre>
</div>
<p>
The above example will create an <code>OscillatorNode</code> for each event, where the frequency is controlled by
the <code>note</code> param. In essence, this is how the WebAudio API output of Strudel works, only with many more
parameters to control synths, samples and effects.
</p>
<h1 data-number="8" id="pattern-alignment-and-combination">
<span class="header-section-number">8</span> Pattern alignment and combination
</h1>
<p>
One core aspect of Strudel, inherited from Tidal, is the flexible way that patterns can be combined, irrespective
of their structure. Its declarative approach means a live coder does not have to think about the details of
<em>how</em> this is done, only <em>what</em> is to be done.
</p>
<p>
As a simple example, consider two number patterns <code>"0 [1 2] 3"</code>, and <code>"10 20"</code>. The first
has three contiguous steps of equal lengths, with the second step broken down into two substeps, giving four
events in total. There are a very large number of ways in which the structure of these two patterns could be
combined, but the default method in both Strudel and Tidal is to line up the cycles of the two patterns, and then
take events from the first pattern and match them with those in the second pattern. Therefore, the following two
lines are equivalent:
</p>
<div class="sourceCode" id="cb14">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;0 [1 2] 3&quot;</span><span class="op">.</span><span class="fu">add</span>(<span class="st">&quot;10 20&quot;</span>)</span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;10 [11 22] 23&quot;</span></span></code></pre>
</div>
<p>
Where the events only partially overlap, they are treated as fragments of the event in the first pattern. This is
a little difficult to conceptualise, but lets start by comparing the two patterns in the following example:
</p>
<div class="sourceCode" id="cb15">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;0 1 2&quot;</span><span class="op">.</span><span class="fu">add</span>(<span class="st">&quot;10 20&quot;</span>)</span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;10 [11 21] 20&quot;</span></span></code></pre>
</div>
<p>
They are similar to the previous example in that the number <code>1</code> is split in two, with its two halves
added to <code>10</code> and <code>20</code> respectively. However, the <code>11</code> remembers that it is a
fragment of that original <code>1</code> event, and so is treated as having a duration of a third of a cycle,
despite only being active for a sixth of a cycle. Likewise, the <code>21</code> is also a fragment of that
original <code>1</code> event, but a fragment of its second half. Because the start of its event is missing, it
wouldnt actually trigger a sound (unless it underwent further pattern transformations/combinations).
</p>
<p>
In practice, the effect of this default, implicit method for combining two patterns is that the second pattern is
added <em>in</em> to the first one, and indeed this can be made explicit:
</p>
<div class="sourceCode" id="cb16">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;0 1 2&quot;</span><span class="op">.</span><span class="at">add</span><span class="op">.</span><span class="fu">in</span>(<span class="st">&quot;10 20&quot;</span>)</span></code></pre>
</div>
<p>This makes way for other ways to align the pattern, and several are already defined, in particular:</p>
<ul>
<li>
<code>in</code> - as explained above, aligns cycles, and applies values from the pattern on the right
<em>in</em> to the pattern on the left.
</li>
<li>
<code>out</code> - as with <code>in</code>, but values are applied <em>out</em> of the pattern on the left
(i.e. <em>in</em> to the one on the right).
</li>
<li>
<code>mix</code> - structures from both patterns are combined, so that the new events are not fragments but are
created at intersections of events from both sides.
</li>
<li>
<code>squeeze</code> - cycles from the pattern on the right are squeezed into events on the left. So that
e.g. <code>"0 1 2".add.squeeze("10 20")</code> is equivalent to <code>"[10 20] [11 21] [12 22]"</code>.
</li>
<li>
<code>squeezeout</code> - as with <code>squeeze</code>, but cycles from the left are squeezed into events on the
right. So, <code>"0 1 2".add.squeezeout("10 20")</code> is equivalent to <code>[10 11 12] [20 21 22]</code>.
</li>
<li>
<code>trig</code> is similar to <code>squeezeout</code> in that cycles from the right are aligned with events on
the left. However those cycles are not squeezed, rather they are truncated to fit the event. So
<code>"0 1 2 3 4 5 6 7".add.trig("10 [20 30]")</code> would be equivalent to
<code>10 11 12 13 20 21 30 31</code>. In effect, events on the right trigger cycles on the left.
</li>
<li>
<code>trigzero</code> is similar to <code>trig</code>, but the pattern is triggered from its very first cycle,
rather than from the current cycle. <code>trig</code> and <code>trigzero</code> therefore only give different
results where the leftmost pattern differs from one cycle to the next.
</li>
</ul>
<p>
We will save going deeper into the background, design and practicalities of these alignment functions for future
publications. However in the next section, we take them as a case study for looking at the different design
affordances offered by Haskell to Tidal, and JavaScript to Strudel.
</p>
<h1 data-number="9" id="comparing-strudel-and-haskell-in-use">
<span class="header-section-number">9</span> Comparing Strudel and Haskell in use
</h1>
<p>
Unlike Haskell, JavaScript lacks the ability to define custom infix operators, or change the meaning of existing
ones. So the above Strudel example of <code>"0 1 2".add.out("10 20")</code> is equivalent to the Tidal expression
<code>"0 1 2" +| "10 20"</code>, where the vertical bar in the operator <code>+|</code> stands for
<code>out</code> (where <code>a |+ b</code> would be equivalent of <code>a.add.in(b)</code>).
</p>
<p>
From this we can already see that Tidal tends towards brevity through mixing infix operators with functions, and
Strudel tends towards spelling out operations which are joined together with the <code>.</code> operator. This
then is the design trade-off of Tidals tersity, versus Strudels simplicity.
</p>
<p>To demonstrate this, consider the following Tidal pattern:</p>
<pre
class="tidal"
><code>iter 4 $ every 3 (||+ n &quot;10 20&quot;) $ (n &quot;0 1 3&quot;) # s &quot;triangle&quot; # crush 4</code></pre>
<p>This can be directly translated to the Strudel equivalent:</p>
<div class="sourceCode" id="cb18">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="fu">iter</span>(<span class="dv">4</span><span class="op">,</span> <span class="fu">every</span>(<span class="dv">3</span><span class="op">,</span> add<span class="op">.</span><span class="fu">squeeze</span>(<span class="st">&quot;10 20&quot;</span>)<span class="op">,</span> <span class="fu">n</span>(<span class="st">&quot;0 1 3&quot;</span>)<span class="op">.</span><span class="fu">s</span>(<span class="st">&quot;triangle&quot;</span>)<span class="op">.</span><span class="fu">crush</span>(<span class="dv">4</span>)))</span></code></pre>
</div>
<p>Although for a more canonical Strudel expression, we would reorder it as:</p>
<div class="sourceCode" id="cb19">
<pre
class="sourceCode js"
><code class="sourceCode javascript"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="fu">n</span>(<span class="st">&quot;0 1 3&quot;</span>)<span class="op">.</span><span class="fu">every</span>(<span class="dv">3</span><span class="op">,</span> add<span class="op">.</span><span class="fu">squeeze</span>(<span class="st">&quot;10 20&quot;</span>))<span class="op">.</span><span class="fu">iter</span>(<span class="dv">4</span>)<span class="op">.</span><span class="fu">s</span>(<span class="st">&quot;triangle&quot;</span>)<span class="op">.</span><span class="fu">crush</span>(<span class="dv">4</span>)</span></code></pre>
</div>
<p>
The Strudel example uses the <code>.</code> method call operator for all operations and combinations, whereas the
Tidal example has <code>#</code> for the default method for combining patterns and uses infix operators for other
methods. The lack of parenthesis in the Tidal example is partly due to the way that arguments are applied to
Haskells functions, and partly due to the use of the <code>$</code> operator as an alternative way to establish
precedence and control the order of evaluation.
</p>
<p>
Considering the above, we argue that the Haskell syntax is a little cleaner, but that the Strudel syntax is easier
to learn. Our informal observation is that while Haskells dollar <code>$</code> operator is very useful in making
code easier to work with, it is one of the most difficult aspects of Tidal use for beginners to learn. On the
other hand, the deeper levels of parenthesis in Strudel code can be difficult to keep track of, especially while
coding under pressure of live musical performance. However this difficulty can be largely be mitigated by
reordering expressions, and further mitigated by supporting editor features.
</p>
<p>
With Strudel, we have little choice but to embrace the affordances and constraints offered by JavaScript, and
while designing a domain-specific language entirely based on method calls is a challenge, through creative
adoption of functional programming techniques like partial application, we are so far very happy with the results.
Tidals functional reactive approach to pattern-making has in general translated well to JavaScript, and
opportunities and constraints have overall traded off to create a very approachable and useable live coding
environment.
</p>
<h2 data-number="9.1" id="the-trade-off-of-flexible-typing">
<span class="header-section-number">9.1</span> The trade-off of flexible typing
</h2>
<p>
We have identified one problem with porting Tidal to JavaScript where we have missed Haskells strict typing and
type inference. In both Tidal and Strudel, time is rational, where any point in time is represented as the ratio
of two integers. This allows representation of musical ratios such that are impossible to represent accurately
using the more common floating point numbers. However while libraries are available that support rational numbers
in JavaScript, the lack of strict typing means that it is easy to implement pattern methods where computationally
expensive conversion from floating point to rational numbers are performed late, and therefore often enough to
overload the CPUs, due to the large number of iterative calculations required to estimate a ratio for a given
floating point number. To mitigate this problem, we might consider moving to TypeScript in the future.
</p>
<h1 data-number="10" id="future-outlook"><span class="header-section-number">10</span> Future Outlook</h1>
<p>
The project is still young, with many features on the horizon. As general guiding principles, Strudel aims to be
</p>
<ol type="1">
<li>accessible</li>
<li>consistent with Tidals approach to pattern</li>
<li>modular and extensible</li>
</ol>
<p>
While Haskells type system makes it a great language for the ongoing development of Tidals inner representation
of pattern, JavaScripts vibrant ecosystem, flexibility and accessibility makes it a great host for more ad-hoc
experiments, including interface design. For the future, it is planned to integrate additional alternative sound
engines such as Glicol <span class="citation" data-cites="lanChaosprintGlicol2022">(Lan [2020] 2022)</span> and
Faust
<span class="citation" data-cites="FaustProgrammingLanguage2022"
>(<em>Faust - Programming Language for Audio Applications and Plugins</em> [2016] 2022)</span
>. Strudel is already approaching feature parity with Tidal, but there are more Tidal functions to be ported, and
work to be done to improve compatibility with Tidals mininotation. Tidal version 2.0 is under development, which
brings a new representation for sequences to its patterns, which will then be brought to Strudel. Besides sound,
other ways to render events are being explored, such as graphical, and choreographic output. We are also looking
into alternative ways of editing patterns, including multi-user editing for network music, parsing a novel syntax
to escape the constraints of javascript, and developing hardware/e-textile interfaces. In summary, there is a lot
of fun ahead.
</p>
<h1 data-number="11" id="links"><span class="header-section-number">11</span> Links</h1>
<p>
The Strudel REPL is available at <a href="https://strudel.cc" class="uri">https://strudel.cc</a>, including an
interactive tutorial. The repository is at
<a href="https://github.com/tidalcycles/strudel" class="uri">https://github.com/tidalcycles/strudel</a>, all the
code is open source under the AGPL-3.0 License.
</p>
<h1 data-number="12" id="acknowledgments"><span class="header-section-number">12</span> Acknowledgments</h1>
<p>
Thanks to the Strudel and wider Tidal, live coding, WebAudio and free/open source software communities for
inspiration and support. Alex McLeans work on this project is supported by a UKRI Future Leaders Fellowship
[grant number MR/V025260/1].
</p>
<h1 class="unnumbered" id="references">References</h1>
<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">
<div id="ref-FaustProgrammingLanguage2022" class="csl-entry" role="doc-biblioentry">
<em>Faust - Programming Language for Audio Applications and Plugins</em>. (2016) 2022. C++. GRAME.
<a href="https://github.com/grame-cncm/faust">https://github.com/grame-cncm/faust</a>.
</div>
<div id="ref-jackHydra2022" class="csl-entry" role="doc-biblioentry">
Jack, Olivia. (2022) 2022. <em>Hydra</em>.
<a href="https://github.com/ojack/hydra">https://github.com/ojack/hydra</a>.
</div>
<div id="ref-lanChaosprintGlicol2022" class="csl-entry" role="doc-biblioentry">
Lan, Qichao. (2020) 2022. <em>Chaosprint/Glicol</em>. Rust.
<a href="https://github.com/chaosprint/glicol">https://github.com/chaosprint/glicol</a>.
</div>
<div id="ref-mcleanAlgorithmicPattern2020a" class="csl-entry" role="doc-biblioentry">
Mclean, Alex. 2020. <span>“Algorithmic Pattern.”</span> In
<em>Proceedings of the International Conference on New Interfaces for Musical Expression</em>, 265--270.
Birmingham, UK. <a href="https://zenodo.org/record/4813352">https://zenodo.org/record/4813352</a>.
</div>
<div id="ref-mcleanFeedforward2020" class="csl-entry" role="doc-biblioentry">
McLean, Alex. 2020. <span>“Feedforward.”</span> In
<em>Proceedings of New Interfaces for Musical Expression</em>. Birmingham.
<a href="https://zenodo.org/record/6353969">https://zenodo.org/record/6353969</a>.
</div>
<div id="ref-mcleanTidalVortexZero2022" class="csl-entry" role="doc-biblioentry">
McLean, Alex, Raphaël Forment, Sylvain Le Beux, and Damián Silvani. 2022. <span>“TidalVortex Zero.”</span> In
<em>Proceedings of the 7th International Conference on Live Coding</em>. Limerick, Ireland: Zenodo.
<a href="https://doi.org/10.5281/zenodo.6456380">https://doi.org/10.5281/zenodo.6456380</a>.
</div>
<div id="ref-ogbornDktr0WebDirt2022" class="csl-entry" role="doc-biblioentry">
Ogborn, David. (2016) 2022. <em>Dktr0/WebDirt</em>. JavaScript.
<a href="https://github.com/dktr0/WebDirt">https://github.com/dktr0/WebDirt</a>.
</div>
<div id="ref-ogbornEstuaryBrowserbasedCollaborative2017" class="csl-entry" role="doc-biblioentry">
Ogborn, David, Jamie Beverley, Luis Navarro del Angel, Eldad Tsabary, and Alex McLean. 2017.
<span>“Estuary: Browser-Based Collaborative Projectional Live Coding of Musical Patterns.”</span> In
<em>Proceedings of the International Conference on Live Coding</em>, 11. Morelia.
</div>
<div id="ref-robertsGibberLiveCoding2012" class="csl-entry" role="doc-biblioentry">
Roberts, Charles, and Joann Kuchera-morin. 2012. <span>“Gibber: Live Coding Audio in the Browser.”</span> In
<em>In Proceedings of the 2012 International Computer Music Conference</em>.
</div>
<div id="ref-StrudelWAC2022" class="csl-entry" role="doc-biblioentry">
Roos, Felix, and Alex McLean. 2022. <span>“Strudel: Algorithmic Patterns for the Web.”</span> In. Zenodo.
<a href="https://doi.org/10.5281/zenodo.6768844">https://doi.org/10.5281/zenodo.6768844</a>.
</div>
<div id="ref-solomonPurescriptocarina2022" class="csl-entry" role="doc-biblioentry">
Solomon, Mike. (2021) 2022. <em>Purescript-Ocarina</em>. PureScript.
<a href="https://github.com/mikesol/purescript-ocarina">https://github.com/mikesol/purescript-ocarina</a>.
</div>
<div id="ref-SuperDirt2022" class="csl-entry" role="doc-biblioentry">
<em>SuperDirt</em>. (2015) 2022. SuperCollider. musikinformatik.
<a href="https://github.com/musikinformatik/SuperDirt">https://github.com/musikinformatik/SuperDirt</a>.
</div>
<div id="ref-toussaintEuclideanAlgorithmGenerates2005" class="csl-entry" role="doc-biblioentry">
Toussaint, Godfried. 2005. <span>“The Euclidean Algorithm Generates Traditional Musical Rhythms.”</span> In
<em>In Proceedings of BRIDGES: Mathematical Connections in Art, Music and Science</em>, 4756.
<a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.62.231"
>http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.62.231</a
>.
</div>
<div id="ref-CsoundWebAssembly" class="csl-entry" role="doc-biblioentry">
Yi, Steven, Victor Lazzarini, and Edward Costello. 2018.
<span>“WebAssembly AudioWorklet Csound.”</span> In. Berlin, Germany.
<a href="https://mural.maynoothuniversity.ie/16018/">https://mural.maynoothuniversity.ie/16018/</a>.
</div>
</div>
</body>
</html>
@@ -451,7 +451,7 @@ While Haskell's type system makes it a great language for the ongoing developmen
# Links
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

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

+63
View File
@@ -0,0 +1,63 @@
$if(false)$ This is a pandoc template and should not be edited. $endif$
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" $if(lang)$ lang="$lang$" xml:lang="$lang$" $endif$>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
$for(author-meta)$
<meta name="author" content="$author-meta$" />
$endfor$ $if(date-meta)$
<meta name="date" content="$date-meta$" />
$endif$
<title>$if(title-prefix)$$title-prefix$ - $endif$$pagetitle$</title>
<style type="text/css">
code {
white-space: pre;
}
</style>
$if(quotes)$
<style type="text/css">
q {
quotes: '“' '”' '' '';
}
</style>
$endif$ $if(highlighting-css)$
<style type="text/css">
$highlighting-css$
</style>
$endif$
<link rel="stylesheet" href="css/iclc.css" $if(html5)$$else$type="text/css" $endif$ />
$for(css)$
<link rel="stylesheet" href="$css$" $if(html5)$$else$type="text/css" $endif$ />
$endfor$ $if(math)$ $math$ $endif$ $for(header-includes)$ $header-includes$ $endfor$
</head>
<body>
$for(include-before)$ $include-before$ $endfor$ $if(title)$
<div id="$idprefix$header">
<h1 class="title">$title$</h1>
$if(subtitle)$
<h1 class="subtitle">$subtitle$</h1>
$endif$
<ul id="authorlist">
$for(author)$
<li>$author$</li>
$endfor$
</ul>
$if(date)$
<h3 class="date">$date$</h3>
$endif$
</div>
$endif$ $if(toc)$
<div id="$idprefix$TOC">$toc$</div>
$endif$
<h2 class="abstract">Abstract</h2>
<div id="abstract">
$if(abstract)$ $abstract$ $else$ Please provide an abstract in the metadata block at the top of your markdown
document. Refer to template.txt for details. $endif$
</div>
$body$ $for(include-after)$ $include-after$ $endfor$
</body>
</html>
+197
View File
@@ -0,0 +1,197 @@
This document introduces you to Strudel in a technical sense.
It is rather out of date, but there might still be useful info below.
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="strudelflow.png" width="600" />
## 1. End User Code
The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://github.com/tidalcycles/strudel/tree/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://github.com/tidalcycles/strudel/blob/talk/talk/public/shiftflow.png?raw=true" 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://github.com/tidalcycles/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://github.com/tidalcycles/strudel/blob/main/packages/mini/krill.pegjs), living in the [mini package](https://github.com/tidalcycles/strudel/tree/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="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://github.com/tidalcycles/strudel/tree/main/packages/webaudio)
- MIDI `.midi()` see [/midi](https://github.com/tidalcycles/strudel/tree/main/packages/midi)
- OSC `.osc()` see [/osc](https://github.com/tidalcycles/strudel/tree/main/packages/osc)
- Serial `.serial()` see [/serial](https://github.com/tidalcycles/strudel/tree/main/packages/serial)
- Tone.js `.tone()` (deprecated?) [/tone](https://github.com/tidalcycles/strudel/tree/main/packages/tone)
- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://github.com/tidalcycles/strudel/tree/main/packages/webdirt)
- Speech `.speak()` (experimental) part of [/core](https://github.com/tidalcycles/strudel/tree/main/packages/core)
These could change, so make sure to check the [packages folder](https://github.com/tidalcycles/strudel/tree/main/packages).
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+3 -3
View File
@@ -11,7 +11,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"titdalcycles",
@@ -23,9 +23,9 @@
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"homepage": "https://codeberg.org/uzu/strudel#readme",
"dependencies": {
"@strudel/web": "workspace:*",
"hs2js": "workspace:*"
+1 -1
View File
@@ -1,6 +1,6 @@
/*
jsdoc-synonyms.js - Add support for @synonym tag
Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/midi/midi.mjs>
Copyright (C) 2023 Strudel contributors - see <https://codeberg.org/uzu/strudel/activity/contributors>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+9 -13
View File
@@ -5,22 +5,24 @@ made into a pattern swatch.
Example: <https://felixroos.github.io/strudel/swatch/>
Please note: These instructions have not been fully tested/adapted since strudel moved to codeberg from github. PRs welcome!
## deploy
### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git)
### 1. fork the [strudel repo on codeberg](https://codeberg.org/uzu/strudel.git)
### 2. clone your fork to your machine `git clone https://github.com/<your-username>/strudel.git strudel && cd strudel`
### 2. clone your fork to your machine `git clone https://codeberg.org/<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`
### 5. edit `website/public/CNAME` to contain `<your-username>.codeberg.page/strudel`
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`, like this
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.codeberg.page` and base `/strudel`, like this
```js
const site = 'https://<your-username>.github.io';
const site = 'https://<your-username>.codeberg.page';
const base = '/strudel';
```
@@ -30,15 +32,9 @@ const base = '/strudel';
git add . && git commit -m "site config" && git push --set-upstream origin
```
### 8. deploy to github pages
### 8. deploy to codeberg 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).
### 9. view your patterns at `<your-username>.codeberg.page/strudel/swatch/`
### 10. optional: automatic deployment
+3 -3
View File
@@ -31,7 +31,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@@ -43,7 +43,7 @@
"author": "Alex McLean <alex@slab.org> (https://slab.org)",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://strudel.cc",
"dependencies": {
@@ -74,4 +74,4 @@
"vitest": "^3.0.4",
"vite-plugin-bundle-audioworklet": "workspace:*"
}
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
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://github.com/tidalcycles/strudel/wiki/Technical-Manual) 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.
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "1.2.0",
"version": "1.2.2",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
@@ -13,7 +13,7 @@
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@@ -28,9 +28,9 @@
],
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"homepage": "https://codeberg.org/uzu/strudel#readme",
"dependencies": {
"@codemirror/autocomplete": "^6.18.4",
"@codemirror/commands": "^6.8.0",
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* @name Cutie Pi
/*
* Cutie Pi
* by Switch Angel
*/
import { tags as t } from '@lezer/highlight';
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* @name androidstudio
/*
* androidstudio
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* @name Atom One
/*
* Atom One
* Atom One dark syntax theme
*
* https://github.com/atom/one-dark-syntax
+2 -3
View File
@@ -1,6 +1,5 @@
/**
* @name darcula
* @author darcula
/*
* darcula
* Name: IntelliJ IDEA darcula theme
* From IntelliJ IDEA by JetBrains
*/
+1 -2
View File
@@ -1,6 +1,5 @@
/**
/*
* @name dracula
* @author dracula
* Michael Kaminsky (http://github.com/mkaminsky11)
* Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
*/
+3 -3
View File
@@ -1,6 +1,6 @@
/**
* @name duotone
* @author Bram de Haan
/*
* duotone
* author Bram de Haan
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
*/
import { tags as t } from '@lezer/highlight';
+3 -3
View File
@@ -1,6 +1,6 @@
/**
* @name duotone
* @author Bram de Haan
/*
* duotone
* author Bram de Haan
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
*/
import { tags as t } from '@lezer/highlight';
-3
View File
@@ -1,6 +1,3 @@
/**
* @name github
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
-3
View File
@@ -1,6 +1,3 @@
/**
* @name github
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* @name Atom One
/*
* Atom One
* Atom One dark syntax theme
*
* https://github.com/atom/one-dark-syntax
+3 -4
View File
@@ -1,7 +1,6 @@
/**
* @name gruvbox-dark
* @author morhetz
* Name: Gruvbox
/*
* gruvbox-dark
* author morhetz
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css
*/
import { tags as t } from '@lezer/highlight';
+3 -4
View File
@@ -1,7 +1,6 @@
/**
* @name gruvbox-light
* @author morhetz
* Name: Gruvbox
/*
* gruvbox-light
* author morhetz
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css
*/
import { tags as t } from '@lezer/highlight';
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* @name Atom One
/*
* Atom One
* Atom One dark syntax theme
*
* https://github.com/atom/one-dark-syntax
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* @name Atom One
/*
* Atom One
* Atom One dark syntax theme
*
* https://github.com/atom/one-dark-syntax
+1 -1
View File
@@ -113,7 +113,7 @@ self.onconnect = function (e) {
port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
};
// used to consistently schedule events, for use in a service worker - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/clockworker.mjs>
// used to consistently schedule events, for use in a service worker - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/clockworker.mjs>
function createClock(
getTime,
callback, // called slightly before each cycle
+68 -7
View File
@@ -1,6 +1,6 @@
/*
controls.mjs - Registers audio controls for pattern manipulation and effects.
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/controls.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/controls.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -106,7 +106,7 @@ export const { source, src } = registerControl('source', 'src');
* @example
* s("bd sd [~ bd] sd,hh*6").n("<0 1>")
*/
// also see https://github.com/tidalcycles/strudel/pull/63
// also see https://codeberg.org/uzu/strudel/pulls/63
export const { n } = registerControl('n');
/**
* Plays the given note name or midi number. A note name consists of
@@ -358,7 +358,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], '
* s("bd sd [~ bd] sd").bpf(500).bpq("<0 1 2 3>")
*
*/
// currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496
// currently an alias of 'bandq' https://codeberg.org/uzu/strudel/issues/496
// ['bpq'],
export const { bandq, bpq } = registerControl('bandq', 'bpq');
/**
@@ -455,6 +455,32 @@ export const { coarse } = registerControl('coarse');
*/
export const { drive } = registerControl('drive');
/**
* Create byte beats with custom expressions
*
* @name byteBeatExpression
* @synonyms bbexpr
*
* @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat
* @example
* s("bytebeat").bbexpr('t*(t>>15^t>>66)')
*
*/
export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr');
/**
* Create byte beats with custom expressions
*
* @name byteBeatStartTime
* @synonyms bbst
*
* @param {number | Pattern} byteBeatStartTime in samples (t)
* @example
* note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope()
*
*/
export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst');
/**
* Allows you to set the output channels on the interface
*
@@ -468,6 +494,41 @@ export const { drive } = registerControl('drive');
*/
export const { channels, ch } = registerControl('channels', 'ch');
/**
* controls the pulsewidth of the pulse oscillator
*
* @name pw
* @param {number | Pattern} pulsewidth
* @example
* note("{f a c e}%16").s("pulse").pw(".8:1:.2")
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1")
*/
export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);
/**
* controls the lfo rate for the pulsewidth of the pulse oscillator
*
* @name pwrate
* @param {number | Pattern} rate
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
*
*/
export const { pwrate } = registerControl('pwrate');
/**
* controls the lfo sweep for the pulsewidth of the pulse oscillator
*
* @name pwsweep
* @param {number | Pattern} sweep
* @example
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
*
*/
export const { pwsweep } = registerControl('pwsweep');
/**
* Phaser audio effect that approximates popular guitar pedals.
*
@@ -804,7 +865,7 @@ export const { fanchor } = registerControl('fanchor');
* s("bd sd [~ bd] sd,hh*8").hpf("<2000 2000:25>")
*
*/
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496
// currently an alias of 'hcutoff' https://codeberg.org/uzu/strudel/issues/496
// ['hpf'],
/**
* Applies a vibrato to the frequency of the oscillator.
@@ -871,7 +932,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq');
* s("bd sd [~ bd] sd,hh*8").lpf(2000).lpq("<0 10 20 30>")
*
*/
// currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496
// currently an alias of 'resonance' https://codeberg.org/uzu/strudel/issues/496
export const { resonance, lpq } = registerControl('resonance', 'lpq');
/**
* DJ filter, below 0.5 is low pass filter, above is high pass filter.
@@ -1237,7 +1298,7 @@ export const { semitone } = registerControl('semitone');
// TODO: synth param
export const { voice } = registerControl('voice');
// voicings // https://github.com/tidalcycles/strudel/issues/506
// voicings // https://codeberg.org/uzu/strudel/issues/506
// chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings
export const { chord } = registerControl('chord');
// which dictionary to use for the voicings
@@ -1471,7 +1532,7 @@ export const { vowel } = registerControl('vowel');
* @name waveloss
*/
export const { waveloss } = registerControl('waveloss');
/*
/**
* Noise crackle density
*
* @name density
+3 -3
View File
@@ -1,6 +1,6 @@
/*
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/neocyclist.mjs>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/cyclist.mjs>
cyclist.mjs - event scheduler for a single strudel instance. for multi-instance scheduler, see - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/neocyclist.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/cyclist.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -65,7 +65,7 @@ export class Cyclist {
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
const duration = hap.duration / this.cps;
// the following line is dumb and only here for backwards compatibility
// see https://github.com/tidalcycles/strudel/pull/1004
// see https://codeberg.org/uzu/strudel/pulls/1004
const deadline = targetTime - phase;
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*
drawLine.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/drawLine.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/drawLine.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+23 -24
View File
@@ -2,7 +2,7 @@
euclid.mjs - Bjorklund/Euclidean/Diaspora rhythms
Copyright (C) 2023 Rohan Drape and strudel contributors
See <https://github.com/tidalcycles/strudel/blob/main/packages/core/euclid.mjs> for authors of this file.
See <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/euclid.mjs> for authors of this file.
The Bjorklund algorithm implementation is ported from the Haskell Music Theory Haskell module by Rohan Drape -
https://rohandrape.net/?t=hmt
@@ -10,9 +10,9 @@ https://rohandrape.net/?t=hmt
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, timeCat, register, silence } from './pattern.mjs';
import { timeCat, register, silence } from './pattern.mjs';
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
import Fraction from './fraction.mjs';
import Fraction, { lcm } from './fraction.mjs';
const left = function (n, x) {
const [ons, offs] = n;
@@ -42,29 +42,26 @@ const _bjork = function (n, x) {
export const bjork = function (ons, steps) {
const inverted = ons < 0;
ons = Math.abs(ons);
const offs = steps - ons;
const x = Array(ons).fill([1]);
const y = Array(offs).fill([0]);
const result = _bjork([ons, offs], [x, y]);
const p = flatten(result[1][0]).concat(flatten(result[1][1]));
if (inverted) {
return p.map((x) => (x === 0 ? 1 : 0));
}
return p;
const absOns = Math.abs(ons);
const offs = steps - absOns;
const ones = Array(absOns).fill([1]);
const zeros = Array(offs).fill([0]);
const result = _bjork([absOns, offs], [ones, zeros]);
const pattern = flatten(result[1][0]).concat(flatten(result[1][1]));
return inverted ? pattern.map((x) => 1 - x) : pattern;
};
/**
* Changes the structure of the pattern to form an euclidean rhythm.
* Euclidian rhythms are rhythms obtained using the greatest common
* Changes the structure of the pattern to form an Euclidean rhythm.
* Euclidean rhythms are rhythms obtained using the greatest common
* divisor of two numbers. They were described in 2004 by Godfried
* Toussaint, a canadian computer scientist. Euclidian rhythms are
* Toussaint, a Canadian computer scientist. Euclidean rhythms are
* really useful for computer/algorithmic music because they can
* describe a large number of rhythms with a couple of numbers.
*
* @memberof Pattern
* @name euclid
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @returns Pattern
* @example
@@ -76,7 +73,7 @@ export const bjork = function (ons, steps) {
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
* @memberof Pattern
* @name euclidRot
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps
* @returns Pattern
@@ -86,13 +83,13 @@ export const bjork = function (ons, steps) {
*/
/**
* @example // A thirteenth century Persian rhythm called Khafif-e-ramal.
* @example // A thirteenth-century Persian rhythm called Khafif-e-ramal.
* note("c3").euclid(2,5)
* @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad.
* note("c3").euclid(3,4)
* @example // Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm.
* note("c3").euclidRot(3,5,2)
* @example // A Ruchenitza rhythm used in a Bulgarian folk-dance.
* @example // A Ruchenitza rhythm used in a Bulgarian folk dance.
* note("c3").euclid(3,7)
* @example // The Cuban tresillo pattern.
* note("c3").euclid(3,8)
@@ -151,8 +148,10 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
* so there will be no gaps.
* @name euclidLegato
* @memberof Pattern
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param rotation offset in steps
* @param pat
* @example
* note("c3").euclidLegato(3,8)
*/
@@ -161,13 +160,13 @@ const _euclidLegato = function (pulses, steps, rotation, pat) {
if (pulses < 1) {
return silence;
}
const bin_pat = _euclidRot(pulses, steps, rotation);
const bin_pat = _euclidRot(pulses, steps, 0);
const gapless = bin_pat
.join('')
.split('1')
.slice(1)
.map((s) => [s.length + 1, true]);
return pat.struct(timeCat(...gapless));
return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps));
};
export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) {
@@ -180,7 +179,7 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
* the resulting sequence
* @name euclidLegatoRot
* @memberof Pattern
* @param {number} pulses the number of onsets / beats
* @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps
* @example
+1 -1
View File
@@ -1,6 +1,6 @@
/*
evaluate.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/evaluate.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/evaluate.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
fraction.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/fraction.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/fraction.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
hap.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/hap.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/hap.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Fraction from './fraction.mjs';
+1 -1
View File
@@ -1,6 +1,6 @@
/*
index.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/index.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/index.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
neocyclist.mjs - event scheduler like cyclist, except recieves clock pulses from clockworker in order to sync across multiple instances.
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/neocyclist.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/neocyclist.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/core",
"version": "1.2.0",
"version": "1.2.2",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
@@ -15,7 +15,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@@ -27,7 +27,7 @@
"author": "Alex McLean <alex@slab.org> (https://slab.org)",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://strudel.cc",
"dependencies": {
+44 -10
View File
@@ -1,6 +1,6 @@
/*
pattern.mjs - Core pattern representation for strudel
Copyright (C) 2025 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/pattern.mjs>
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/pattern.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -2444,9 +2444,14 @@ const _chunk = function (n, func, pat, back = false, fast = false) {
return pat.when(binary_pat, func);
};
export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, false);
});
export const { chunk, slowchunk, slowChunk } = register(
['chunk', 'slowchunk', 'slowChunk'],
function (n, func, pat) {
return _chunk(n, func, pat, false, false);
},
true,
true,
);
/**
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
@@ -2458,9 +2463,14 @@ export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', '
* "0 1 2 3".chunkBack(4, x=>x.add(7))
* .scale("A:minor").note()
*/
export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) {
return _chunk(n, func, pat, true);
});
export const { chunkBack, chunkback } = register(
['chunkBack', 'chunkback'],
function (n, func, pat) {
return _chunk(n, func, pat, true);
},
true,
true,
);
/**
* Like `chunk`, but the cycles of the source pattern aren't repeated
@@ -2474,9 +2484,14 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
* .fastChunk(4, x => x.color('red')).slow(2)
* .scale("C2:major").note()
*/
export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, true);
});
export const { fastchunk, fastChunk } = register(
['fastchunk', 'fastChunk'],
function (n, func, pat) {
return _chunk(n, func, pat, false, true);
},
true,
true,
);
// TODO - redefine elsewhere in terms of mask
export const bypass = register(
@@ -3180,6 +3195,25 @@ export const slice = register(
false, // turns off auto-patternification
);
/**
*
* make something happen on event time
* uses browser timeout which is innacurate for audio tasks
* @name onTriggerTime
* @memberof Pattern
* @returns Pattern
* @example
* s("bd!8").onTriggerTime((hap) => {console.info(hap)})
*/
Pattern.prototype.onTriggerTime = function (func) {
return this.onTrigger((t_deprecate, hap, currentTime, cps = 1, targetTime) => {
const diff = targetTime - currentTime;
window.setTimeout(() => {
func(hap);
}, diff * 1000);
}, false);
};
/**
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
* @name splice
+2 -3
View File
@@ -1,6 +1,6 @@
/*
pick.mjs - methods that use one pattern to pick events from other patterns.
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
Copyright (C) 2024 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/signal.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -156,8 +156,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
return _pick(lookup, pat, true).resetJoin();
});
/**
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
/** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
* @name inhabit
* @synonyms pickSqueeze
+1 -1
View File
@@ -230,7 +230,7 @@ export function repl({
export const getTrigger =
({ getTime, defaultOutput }) =>
async (hap, deadline, duration, cps, t) => {
// TODO: get rid of deadline after https://github.com/tidalcycles/strudel/pull/1004
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
await defaultOutput(hap, deadline, duration, cps, t);
+39 -6
View File
@@ -1,6 +1,6 @@
/*
signal.mjs - continuous patterns
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
Copyright (C) 2024 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/signal.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -488,13 +488,34 @@ export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pair
export const wrandcat = wchooseCycles;
// this function expects pat to be a pattern of floats...
export const perlinWith = (pat) => {
const pata = pat.fmap(Math.floor);
const patb = pat.fmap((t) => Math.floor(t) + 1);
function _perlin(t) {
let ta = Math.floor(t);
let tb = ta + 1;
const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3;
const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a);
return pat.sub(pata).fmap(interp).appBoth(pata.fmap(timeToRand)).appBoth(patb.fmap(timeToRand));
const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb));
return v;
}
export const perlinWith = (tpat) => {
return tpat.fmap(_perlin);
};
function _berlin(t) {
const prevRidgeStartIndex = Math.floor(t);
const nextRidgeStartIndex = prevRidgeStartIndex + 1;
const prevRidgeBottomPoint = timeToRand(prevRidgeStartIndex);
const nextRidgeTopPoint = timeToRand(nextRidgeStartIndex) + prevRidgeBottomPoint;
const currentPercent = (t - prevRidgeStartIndex) / (nextRidgeStartIndex - prevRidgeStartIndex);
const interp = (a, b, t) => {
return a + (b - a) * t;
};
return interp(prevRidgeBottomPoint, nextRidgeTopPoint, currentPercent) / 2;
}
export const berlinWith = (tpat) => {
return tpat.fmap(_berlin);
};
/**
@@ -508,6 +529,18 @@ export const perlinWith = (pat) => {
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));
/**
* Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful,
* like perlin noise but with sawtooth waves), in the range 0..1.
*
* @name berlin
* @example
* // ascending arpeggios
* n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor")
*
*/
export const berlin = berlinWith(time.fmap((v) => Number(v)));
export const degradeByWith = register(
'degradeByWith',
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
+1 -1
View File
@@ -1,6 +1,6 @@
/*
speak.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/speak.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/speak.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
state.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/state.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/state.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
controls.test.mjs - <short description TODO>
Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/controls.test.mjs>
Copyright (C) 2023 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/controls.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
drawLine.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/drawLine.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/drawLine.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+89
View File
@@ -0,0 +1,89 @@
import { bjork } from '../euclid.mjs';
import { describe, expect, it } from 'vitest';
import { fastcat } from '../pattern.mjs';
describe('bjork', () => {
it('should apply bjorklund to ons and steps', () => {
expect(bjork(3, 8)).toStrictEqual([1, 0, 0, 1, 0, 0, 1, 0]);
expect(bjork(-3, 8)).toStrictEqual([0, 1, 1, 0, 1, 1, 0, 1]);
expect(bjork(8, 8)).toStrictEqual([1, 1, 1, 1, 1, 1, 1, 1]);
expect(bjork(-8, 8)).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0]);
expect(bjork(5, 8)).toStrictEqual([1, 0, 1, 1, 0, 1, 1, 0]);
});
});
describe('euclid', () => {
it('Can create euclid', () => {
expect(
fastcat('a')
.euclid(3, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '3/8 → 1/2: a', '3/4 → 7/8: a']);
expect(
fastcat('a')
.euclid(5, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '3/8 → 1/2: a', '5/8 → 3/4: a', '3/4 → 7/8: a']);
});
});
describe('euclidRot', () => {
it('Can create euclidRot', () => {
expect(
fastcat('a')
.euclidRot(3, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '5/8 → 3/4: a']);
expect(
fastcat('a')
.euclidRot(5, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '1/2 → 5/8: a', '5/8 → 3/4: a', '7/8 → 1/1: a']);
});
});
describe('euclidLegato', () => {
it('Can create euclidLegato', () => {
expect(
fastcat('a')
.euclidLegato(3, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 3/8: a', '3/8 → 3/4: a', '3/4 → 1/1: a']);
expect(
fastcat('a')
.euclidLegato(5, 8)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/4: a', '1/4 → 3/8: a', '3/8 → 5/8: a', '5/8 → 3/4: a', '3/4 → 1/1: a']);
});
});
describe('euclidLegatoRot', () => {
it('Can create euclidLegatoRot', () => {
expect(
fastcat('a')
.euclidLegatoRot(3, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/4: a', '1/4 → 5/8: a', '5/8 → 1/1: a']);
expect(
fastcat('a')
.euclidLegatoRot(5, 8, 2)
.firstCycle()
.sort((a, b) => a.part.begin.sub(b.part.begin))
.map((a) => a.showWhole(true)),
).toStrictEqual(['0/1 → 1/4: a', '1/4 → 1/2: a', '1/2 → 5/8: a', '5/8 → 7/8: a', '7/8 → 1/1: a']);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
/*
fraction.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/fraction.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/fraction.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+2 -2
View File
@@ -1,6 +1,6 @@
/*
pattern.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/pattern.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/pattern.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -884,7 +884,7 @@ describe('Pattern', () => {
);
});
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);
});
});
+57 -10
View File
@@ -1,26 +1,30 @@
/*
util.test.mjs - Tests for the core 'util' module
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/util.test.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/util.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { pure } from '../pattern.mjs';
import {
isNote,
tokenizeNote,
noteToMidi,
midiToFreq,
freqToMidi,
_mod,
compose,
flatten,
fractionalArgs,
freqToMidi,
getFrequency,
getPlayableNoteValue,
parseNumeral,
parseFractional,
isNote,
midiToFreq,
noteToMidi,
numeralArgs,
fractionalArgs,
parseFractional,
parseNumeral,
rotate,
splitAt,
tokenizeNote,
zipWith,
} from '../util.mjs';
import { describe, it, expect } from 'vitest';
import { describe, expect, it } from 'vitest';
describe('isNote', () => {
it('should recognize notes without accidentals', () => {
@@ -233,3 +237,46 @@ describe('fractionalArgs', () => {
expect(add('q', 2)).toBe(2.25);
});
});
describe('rotate', () => {
it('should rotate array to the left', () => {
expect(rotate([0, 1, 2, 3], 2)).toStrictEqual([2, 3, 0, 1]);
expect(rotate([0, 1, 2, 3], 0)).toStrictEqual([0, 1, 2, 3]);
expect(rotate([0, 1, 2, 3], -3)).toStrictEqual([1, 2, 3, 0]);
expect(rotate([0, 1, 2, 3], 3)).toStrictEqual([3, 0, 1, 2]);
expect(rotate([0], 3)).toStrictEqual([0]);
expect(rotate([], 3)).toStrictEqual([]);
});
});
describe('flatten', () => {
it('should flatten array by one level', () => {
expect(flatten([0, 1, 2, 3])).toStrictEqual([0, 1, 2, 3]);
expect(flatten([0, 1, [2, 3]])).toStrictEqual([0, 1, 2, 3]);
expect(flatten([0, [1, [2, 3]]])).toStrictEqual([0, 1, [2, 3]]);
expect(flatten([0])).toStrictEqual([0]);
expect(flatten([])).toStrictEqual([]);
});
});
describe('splitAt', () => {
it('should split array into two', () => {
expect(splitAt(2, [0, 1, 2, 3])).toStrictEqual([
[0, 1],
[2, 3],
]);
expect(splitAt(0, [0, 1, 2, 3])).toStrictEqual([[], [0, 1, 2, 3]]);
expect(splitAt(-3, [0, 1, 2, 3])).toStrictEqual([[0], [1, 2, 3]]);
expect(splitAt(3, [0, 1, 2, 3])).toStrictEqual([[0, 1, 2], [3]]);
});
});
describe('zipWith', () => {
it('should use the function to combine the two arrays element-wise', () => {
expect(zipWith((a, b) => a + b, [0, 1, 2, 3], [0, 1, 2, 3])).toStrictEqual([0, 2, 4, 6]);
expect(zipWith((a, b) => a + b, [0, 1, 2, 3], [0, 1, 2])).toStrictEqual([0, 2, 4, NaN]);
expect(zipWith((a, b) => a + b, [0, 1, 2], [0, 1, 2, 3])).toStrictEqual([0, 2, 4]);
expect(zipWith((a) => a, [0, 1, 2], [1, 2, 3, 0])).toStrictEqual([0, 1, 2]);
expect(zipWith((a, b) => b, [0, 1, 2], [1, 2, 3, 0])).toStrictEqual([1, 2, 3]);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
/*
value.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/value.test.mjs>
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/activity/contributors>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
timespan.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/timespan.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/timespan.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+1 -1
View File
@@ -1,6 +1,6 @@
/*
ui.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/ui.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/ui.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+3 -1
View File
@@ -1,6 +1,6 @@
/*
util.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/util.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/util.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -162,6 +162,7 @@ export const compose = (...funcs) => pipe(...funcs.reverse());
// Removes 'None' values from given list
export const removeUndefineds = (xs) => xs.filter((x) => x != undefined);
// flattens by one level
export const flatten = (arr) => [].concat(...arr);
export const id = (a) => a;
@@ -237,6 +238,7 @@ export const splitAt = function (index, value) {
return [value.slice(0, index), value.slice(index)];
};
// Uses the function f to combine the arrays xs, ys element-wise
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
export const pairs = function (xs) {
+2 -2
View File
@@ -1,6 +1,6 @@
/*
value.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/value.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/value.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -9,7 +9,7 @@ import { logger } from './logger.mjs';
export function unionWithObj(a, b, func) {
if (b?.value !== undefined && Object.keys(b).length === 1) {
// https://github.com/tidalcycles/strudel/issues/1026
// https://codeberg.org/uzu/strudel/issues/1026
logger(`[warn]: Can't do arithmetic on control pattern.`);
return a;
}
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/csound",
"version": "1.2.1",
"version": "1.2.3",
"description": "csound bindings for strudel",
"main": "index.mjs",
"type": "module",
@@ -13,7 +13,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@@ -28,9 +28,9 @@
],
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"homepage": "https://codeberg.org/uzu/strudel#readme",
"dependencies": {
"@csound/browser": "6.18.7",
"@strudel/core": "workspace:*",
+1 -1
View File
@@ -1,6 +1,6 @@
/*
index.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/desktopbridge/index.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/desktopbridge/index.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+4 -4
View File
@@ -7,7 +7,7 @@
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@@ -19,11 +19,11 @@
"author": "Jade Rowland <jaderowlanddev@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"dependencies": {
"@strudel/core": "workspace:*",
"@tauri-apps/api": "^2.2.0"
},
"homepage": "https://github.com/tidalcycles/strudel#readme"
}
"homepage": "https://codeberg.org/uzu/strudel#readme"
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
draw.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/canvas/draw.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/canvas/draw.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/draw",
"version": "1.2.0",
"version": "1.2.2",
"description": "Helpers for drawing with Strudel",
"main": "index.mjs",
"type": "module",
@@ -13,7 +13,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"titdalcycles",
@@ -25,9 +25,9 @@
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"homepage": "https://codeberg.org/uzu/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*"
},
+1 -1
View File
@@ -1,6 +1,6 @@
/*
pianoroll.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/canvas/pianoroll.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/canvas/pianoroll.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+3 -3
View File
@@ -6,7 +6,7 @@
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@@ -18,7 +18,7 @@
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme"
"homepage": "https://codeberg.org/uzu/strudel#readme"
}
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/gamepad",
"version": "1.2.0",
"version": "1.2.2",
"description": "Gamepad Inputs for strudel",
"main": "index.mjs",
"type": "module",
@@ -13,7 +13,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"titdalcycles",
@@ -25,9 +25,9 @@
"author": "Yuta Nakayama <nkymut@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"homepage": "https://codeberg.org/uzu/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*"
},
+1 -1
View File
@@ -2,7 +2,7 @@
Experimental haskell in javascript interpreter. Many haskell features are not implemented.
This projects mainly exists to be able to write and interpret [Tidal Cycles](https://tidalcycles.org/) code in the browser,
as part of [Strudel](https://github.com/tidalcycles/strudel). This project could only exist thanks to [tree-sitter-haskell](https://github.com/tree-sitter/tree-sitter-haskell).
as part of [Strudel](https://codeberg.org/uzu/strudel). This project could only exist thanks to [tree-sitter-haskell](https://github.com/tree-sitter/tree-sitter-haskell).
## Installation
+3 -3
View File
@@ -16,7 +16,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"haskell",
@@ -25,9 +25,9 @@
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel",
"homepage": "https://codeberg.org/uzu/strudel/",
"dependencies": {
"web-tree-sitter": "^0.24.7"
},
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/hydra",
"version": "1.2.0",
"version": "1.2.2",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"type": "module",
@@ -17,7 +17,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"tidalcycles",
@@ -29,9 +29,9 @@
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"homepage": "https://codeberg.org/uzu/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*",
+1 -1
View File
@@ -1,6 +1,6 @@
/*
midi.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/midi/midi.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/midi/midi.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/midi",
"version": "1.2.1",
"version": "1.2.3",
"description": "Midi API for strudel",
"main": "index.mjs",
"type": "module",
@@ -13,7 +13,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
"url": "git+https://codeberg.org/uzu/strudel.git"
},
"keywords": [
"titdalcycles",
@@ -25,9 +25,9 @@
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
"url": "https://codeberg.org/uzu/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"homepage": "https://codeberg.org/uzu/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*",

Some files were not shown because too many files have changed in this diff Show More