mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-14 06:43:47 -04:00
Compare commits
231 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0ce82e3cd | |||
| e82629de2e | |||
| 8a91549a7b | |||
| cc27d482cd | |||
| 37e640470b | |||
| 5e5e7730c1 | |||
| 89e7d52308 | |||
| 99bb227cf4 | |||
| 70e776e799 | |||
| 66aad0004a | |||
| a968545458 | |||
| 5414bbe85d | |||
| e7e80bfd83 | |||
| c57f5bb429 | |||
| f2608e712b | |||
| 34b41fc26f | |||
| f6e171fed8 | |||
| 2511dcc09e | |||
| 977420e74d | |||
| 2fbdc9fdd7 | |||
| 0a96fa5896 | |||
| 3e0e903d38 | |||
| 591c3fe08f | |||
| 06ebcbff8c | |||
| 142160d79a | |||
| ff2c59a2a6 | |||
| 9b9176325b | |||
| e84c39837f | |||
| 6527c68cda | |||
| 6a09f54b25 | |||
| dc6b766ab7 | |||
| 9eb3c9410d | |||
| 189e650a73 | |||
| a5886bb9d4 | |||
| 877bc95a58 | |||
| 18b1739b89 | |||
| b54139376e | |||
| d64a0ef0eb | |||
| 659071a4ee | |||
| 64f7bc4442 | |||
| 8c97f2e2d0 | |||
| b8c98736c5 | |||
| a0fc52b1ec | |||
| 0693a32736 | |||
| 42a903ecc0 | |||
| da283eb55a | |||
| b12707316a | |||
| 66aa3ac1da | |||
| d81b1ab65c | |||
| 8465517c76 | |||
| 215ab87809 | |||
| e5fe998327 | |||
| 6f75a0a62b | |||
| 0464845262 | |||
| e490774294 | |||
| 34e8a57472 | |||
| 59c8d70714 | |||
| ce7cff2c3b | |||
| 32fe73aba2 | |||
| d6ee10e05c | |||
| 36441e7b73 | |||
| 189daa3942 | |||
| d009b9592e | |||
| f7f1bd63e8 | |||
| 7d24e45692 | |||
| cf55d4c8d1 | |||
| f17a4d045f | |||
| e7e636886d | |||
| 8e1e83f3cf | |||
| 2d64b538e7 | |||
| b717615558 | |||
| e9aef9e4d4 | |||
| 8c4f10464b | |||
| 018649c4f3 | |||
| 5ff59a878d | |||
| a20468b85f | |||
| 9648e35de2 | |||
| cd344cefe0 | |||
| 1f6a313f02 | |||
| 5cbeb227d8 | |||
| 2ab2cc5fc8 | |||
| 3dd667e825 | |||
| 482ebb695a | |||
| 218952b8b6 | |||
| 2e8a8b50df | |||
| 612efad3eb | |||
| e7839a09a1 | |||
| 3292f88810 | |||
| f22f89347e | |||
| de15d79edf | |||
| 23e5358ae4 | |||
| 3b6777e259 | |||
| ed5bf3b3fc | |||
| b089328da3 | |||
| 784c711870 | |||
| 34b10b6c84 | |||
| 450f9685f3 | |||
| 03090e399c | |||
| ed58d26da6 | |||
| e3605590e5 | |||
| d55ef8e843 | |||
| efc2333360 | |||
| 92626e7c20 | |||
| ad113b3888 | |||
| 39c45bc2b2 | |||
| 899af88bd9 | |||
| c2b8cc4b82 | |||
| 40ed7b5382 | |||
| 5d829419ba | |||
| e26da854c0 | |||
| a28b7a2d5a | |||
| 83c776d70f | |||
| 051efdc13b | |||
| ef3cd56b0d | |||
| 9017085fa3 | |||
| cdbbe079db | |||
| d6045c943b | |||
| 6ee9dea2ff | |||
| 03cbe4b6d4 | |||
| 784ee576da | |||
| ee3e1217b4 | |||
| 30c80119e9 | |||
| 973ae4c7da | |||
| 4d13b981f1 | |||
| 7691403fd0 | |||
| 3b6e3624be | |||
| 51f0bab1f8 | |||
| dae1560c40 | |||
| 82f0dbb750 | |||
| fb7d76a2ab | |||
| 26225e4ea7 | |||
| ab12c6ae0a | |||
| 4a894b0be2 | |||
| 4ad305550e | |||
| cf5c502a05 | |||
| 2415210be6 | |||
| fe18aa770d | |||
| 2eda047108 | |||
| c8cf1dc712 | |||
| e136e95375 | |||
| 004c0baa59 | |||
| 5f44ae9c4b | |||
| 66828e17d3 | |||
| b2f90ecb3b | |||
| ccf2a6de52 | |||
| 9416f317d4 | |||
| 75b6f59212 | |||
| 84238c8999 | |||
| 2d855cf5e8 | |||
| dd04d49c75 | |||
| 34874862ab | |||
| 0361bc629f | |||
| 1fdd87a4f3 | |||
| 16e9aca222 | |||
| 4a3d5ca44f | |||
| 6c0e8a0833 | |||
| 7336ff0ded | |||
| 735f57e276 | |||
| eb4f1f69f6 | |||
| e7e321fe03 | |||
| bf3de368f0 | |||
| d645cc6fe5 | |||
| 6fe11be218 | |||
| cf7fc23981 | |||
| bfdfab0a00 | |||
| edb347e85a | |||
| 63fcce79e0 | |||
| 8defdc424c | |||
| 16b2fc0632 | |||
| d0a544d7a7 | |||
| 9b3c6b9f94 | |||
| 6327e17feb | |||
| 15fe2734e9 | |||
| 1dd1ce254f | |||
| 3ea91adfd6 | |||
| ce5f425142 | |||
| 671a22fc22 | |||
| 976955dae3 | |||
| a6ad58a375 | |||
| 681eed3d0e | |||
| 81a9811803 | |||
| 72b582c605 | |||
| 2f0891d4e5 | |||
| a7a283a99e | |||
| d9e44dcda6 | |||
| acd5661106 | |||
| b0b2b6a120 | |||
| 664d76cf1b | |||
| a901e2e387 | |||
| 82b14b5292 | |||
| 05bcd5e32f | |||
| 077556e7cb | |||
| 4cc2de9b7e | |||
| b0c0a393a1 | |||
| 168269a839 | |||
| 34771f03d4 | |||
| 504fe9ed40 | |||
| caf6686d01 | |||
| c063fb6b1c | |||
| e3680b96de | |||
| 688e275e0c | |||
| 2e33569786 | |||
| 46fe6d3c4d | |||
| 9220b95677 | |||
| 5b27b19972 | |||
| 5a72ea94b1 | |||
| eac6f27ca4 | |||
| f2330a2307 | |||
| 2dd528134a | |||
| fef50e654c | |||
| 1b7fbecf50 | |||
| 117cf7e18a | |||
| ae15bb7274 | |||
| bee8c81254 | |||
| 8ad48e1633 | |||
| 4a181d5b33 | |||
| d5ab6b7211 | |||
| 4ba9d47bff | |||
| c6c5a3035c | |||
| e024ae2142 | |||
| 19df19375d | |||
| cba96049a3 | |||
| 1509ff1793 | |||
| 6a33032da7 | |||
| 6c05a4eb7b | |||
| 16472b59dd | |||
| 7df663e87d | |||
| 3159f66560 | |||
| 0d900c0134 | |||
| 66a27a609c | |||
| 496d60dc1c |
@@ -150,6 +150,7 @@ Important: Always publish with `pnpm`, as `npm` does not support overriding main
|
||||
|
||||
|
||||
## useful commands
|
||||
|
||||
```sh
|
||||
#regenerate the test snapshots (ex: when updating or creating new pattern functions)
|
||||
pnpm snapshot
|
||||
@@ -160,6 +161,81 @@ pnpm run osc
|
||||
#build the standalone version
|
||||
pnpm tauri build
|
||||
```
|
||||
|
||||
## version tag patching
|
||||
|
||||
here's a little guide on how to patch patterns in the database to prevent breaking old patterns due to breaking changes in newer versions.
|
||||
|
||||
the general tactic is to use `// @version x.y` to tag a pattern with a specific strudel version. when a pattern is evaluated, this metadata will de-activate any breaking changes that came after the specified version.
|
||||
for example, in version 1.1, the default value for `fanchor` was changed from `0.5` to `0`.
|
||||
if play a pattern that was made before that change, sounds that use filter evenlopes can sound very different, so by adding `// @version 1.0` will make it sound like it used to.
|
||||
before releasing a new version with breaking changes, we can edit all patterns in the database, inserting the version tag they were created under:
|
||||
|
||||
as an example, to release version 1.2, do the following:
|
||||
|
||||
1. get date range
|
||||
|
||||
```sh
|
||||
# get date of last version:
|
||||
git log -1 --format=%aI @strudel/core@1.1.0
|
||||
# 2024-05-31T23:07:26+02:00
|
||||
|
||||
# get date of current version:
|
||||
git log -1 --format=%aI @strudel/core@1.2.0
|
||||
# 2025-05-01T12:39:24+02:00
|
||||
# might also use todays timestamp if version is not yet released
|
||||
```
|
||||
|
||||
now we know, all patterns between these 2 dates have to receive a version tag (unless they already have one).
|
||||
|
||||
2. get patterns in question
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM code_v1
|
||||
WHERE code NOT LIKE '%@version%'
|
||||
AND created_at > '2024-05-31T23:07:26+02:00'
|
||||
AND created_at < '2025-05-01T12:39:24+02:00'
|
||||
ORDER BY created_at ASC;
|
||||
```
|
||||
|
||||
this gives us all unversioned patterns that were saved between 1.1.0 and 1.2.0. in this case, it's 9373 patterns!
|
||||
|
||||
3. insert version tags
|
||||
|
||||
we are now ready to insert the version tag to these patterns.
|
||||
before updating thousands of patterns, it's probably a good idea to test if a single one gets udpated:
|
||||
|
||||
```sql
|
||||
UPDATE code_v1
|
||||
SET code = code || E'\n// @version 1.1'
|
||||
WHERE hash = 'Ns2sMB40yIw4';
|
||||
```
|
||||
|
||||
after [verifying](https://strudel.cc/?Ns2sMB40yIw4) that the version tag has been added, let's insert it everywhere:
|
||||
|
||||
```sql
|
||||
UPDATE code_v1
|
||||
SET code = code || E'\n// @version 1.1'
|
||||
WHERE code NOT LIKE '%@version%'
|
||||
AND created_at > '2024-05-31T23:07:26+02:00'
|
||||
AND created_at < '2025-05-01T12:39:24+02:00'
|
||||
```
|
||||
|
||||
4. verify
|
||||
|
||||
we can verify that the edits worked by querying all patterns that contain the new version tag:
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM code_v1
|
||||
WHERE code LIKE '%@version 1.1%'
|
||||
AND created_at > '2024-05-31T23:07:26+02:00'
|
||||
AND created_at < '2025-05-01T12:39:24+02:00'
|
||||
ORDER BY created_at ASC;
|
||||
```
|
||||
|
||||
|
||||
## Have Fun
|
||||
|
||||
Remember to have fun, and that this project is driven by the passion of volunteers!
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
Live coding patterns on the web
|
||||
https://strudel.cc/
|
||||
|
||||
Development is moving to https://codeberg.org/uzu/strudel
|
||||
|
||||
- Try it here: <https://strudel.cc>
|
||||
- Docs: <https://strudel.cc/learn>
|
||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||
@@ -38,13 +36,7 @@ Licensing info for the default sound banks can be found over on the [dough-sampl
|
||||
|
||||
## Contributing
|
||||
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
|
||||
|
||||
<a href="https://codeberg.org/uzu/strudel/activity/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=tidalcycles/strudel" />
|
||||
</a>
|
||||
|
||||
Made with [contrib.rocks](https://contrib.rocks).
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md). You can find the full list of contributors [here](https://codeberg.org/uzu/strudel/activity/contributors).
|
||||
|
||||
## Community
|
||||
|
||||
@@ -53,3 +45,5 @@ There is a #strudel channel on the TidalCycles discord: <https://discord.com/inv
|
||||
You can also ask questions and find related discussions on the tidal club forum: <https://club.tidalcycles.org/>
|
||||
|
||||
The discord and forum is shared with the haskell (tidal) and python (vortex) siblings of this project.
|
||||
|
||||
We also have a mastodon account: <a rel="me" href="https://social.toplap.org/@strudel">social.toplap.org/@strudel</a>
|
||||
|
||||
@@ -83,4 +83,14 @@ export default [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Properties provided by AudioWorkletGlobalScope
|
||||
files: ['packages/superdough/worklets.mjs'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
currentTime: 'readonly',
|
||||
sampleRate: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
jsdoc-synonyms.js - Add support for @synonym tag
|
||||
jsdoc-synonyms.js - Add support for @synonyms tag
|
||||
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/>.
|
||||
*/
|
||||
|
||||
@@ -1,68 +1,93 @@
|
||||
import jsdoc from '../../doc.json';
|
||||
// import { javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { h } from './html';
|
||||
|
||||
function plaintext(str) {
|
||||
const escapeHtml = (str) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerText = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
const getInnerText = (html) => {
|
||||
var div = document.createElement('div');
|
||||
const stripHtml = (html) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.textContent || div.innerText || '';
|
||||
};
|
||||
|
||||
export function Autocomplete({ doc, label }) {
|
||||
return h`<div class="prose dark:prose-invert max-h-[400px] overflow-auto p-2">
|
||||
<h1 class="pt-0 mt-0">${label || getDocLabel(doc)}</h1>
|
||||
${doc.description}
|
||||
<ul>
|
||||
${doc.params?.map(
|
||||
({ name, type, description }) =>
|
||||
`<li>${name} : ${type.names?.join(' | ')} ${description ? ` - ${getInnerText(description)}` : ''}</li>`,
|
||||
)}
|
||||
</ul>
|
||||
<div>
|
||||
${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)}
|
||||
</div>
|
||||
</div>`[0];
|
||||
/*
|
||||
<pre
|
||||
className="cursor-pointer"
|
||||
onMouseDown={(e) => {
|
||||
console.log('ola!');
|
||||
navigator.clipboard.writeText(example);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{example}
|
||||
</pre>
|
||||
*/
|
||||
}
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
|
||||
const buildParamsList = (params) =>
|
||||
params?.length
|
||||
? `
|
||||
<div class="autocomplete-info-params-section">
|
||||
<h4 class="autocomplete-info-section-title">Parameters</h4>
|
||||
<ul class="autocomplete-info-params-list">
|
||||
${params
|
||||
.map(
|
||||
({ name, type, description }) => `
|
||||
<li class="autocomplete-info-param-item">
|
||||
<span class="autocomplete-info-param-name">${name}</span>
|
||||
<span class="autocomplete-info-param-type">${type.names?.join(' | ')}</span>
|
||||
${description ? `<div class="autocomplete-info-param-desc">${stripHtml(description)}</div>` : ''}
|
||||
</li>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
const buildExamples = (examples) =>
|
||||
examples?.length
|
||||
? `
|
||||
<div class="autocomplete-info-examples-section">
|
||||
<h4 class="autocomplete-info-section-title">Examples</h4>
|
||||
${examples
|
||||
.map(
|
||||
(example) => `
|
||||
<pre class="autocomplete-info-example-code">${escapeHtml(example)}</pre>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
export const Autocomplete = ({ doc, label }) =>
|
||||
h`
|
||||
<div class="autocomplete-info-container">
|
||||
<div class="autocomplete-info-tooltip">
|
||||
<h3 class="autocomplete-info-function-name">${label || getDocLabel(doc)}</h3>
|
||||
${doc.description ? `<div class="autocomplete-info-function-description">${doc.description}</div>` : ''}
|
||||
${buildParamsList(doc.params)}
|
||||
${buildExamples(doc.examples)}
|
||||
</div>
|
||||
</div>
|
||||
`[0];
|
||||
|
||||
const isValidDoc = (doc) => {
|
||||
const label = getDocLabel(doc);
|
||||
return label && !label.startsWith('_') && !['package'].includes(doc.kind);
|
||||
};
|
||||
|
||||
const hasExcludedTags = (doc) =>
|
||||
['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag));
|
||||
|
||||
const jsdocCompletions = jsdoc.docs
|
||||
.filter(
|
||||
(doc) =>
|
||||
getDocLabel(doc) &&
|
||||
!getDocLabel(doc).startsWith('_') &&
|
||||
!['package'].includes(doc.kind) &&
|
||||
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
|
||||
)
|
||||
.filter((doc) => isValidDoc(doc) && !hasExcludedTags(doc))
|
||||
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||
.map((doc) /*: Completion */ => ({
|
||||
.map((doc) => ({
|
||||
label: getDocLabel(doc),
|
||||
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
||||
info: () => Autocomplete({ doc }),
|
||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||
}));
|
||||
|
||||
export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
||||
let word = context.matchBefore(/\w*/);
|
||||
if (word.from == word.to && !context.explicit) return null;
|
||||
export const strudelAutocomplete = (context) => {
|
||||
const word = context.matchBefore(/\w*/);
|
||||
if (word.from === word.to && !context.explicit) return null;
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
options: jsdocCompletions,
|
||||
@@ -74,11 +99,5 @@ export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
||||
};
|
||||
};
|
||||
|
||||
export function isAutoCompletionEnabled(on) {
|
||||
return on
|
||||
? [
|
||||
autocompletion({ override: [strudelAutocomplete] }),
|
||||
//javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
|
||||
]
|
||||
: []; // autocompletion({ override: [] })
|
||||
}
|
||||
export const isAutoCompletionEnabled = (enabled) =>
|
||||
enabled ? [autocompletion({ override: [strudelAutocomplete], closeOnBlur: false })] : [];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
|
||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { history } from '@codemirror/commands';
|
||||
import { history, indentWithTab } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from '@codemirror/language';
|
||||
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
||||
@@ -37,6 +37,14 @@ const extensions = {
|
||||
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
|
||||
isFlashEnabled,
|
||||
keybindings,
|
||||
isTabIndentationEnabled: (on) => (on ? keymap.of([indentWithTab]) : []),
|
||||
isMultiCursorEnabled: (on) =>
|
||||
on
|
||||
? [
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
|
||||
]
|
||||
: [],
|
||||
};
|
||||
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||
|
||||
@@ -51,6 +59,8 @@ export const defaultSettings = {
|
||||
isFlashEnabled: true,
|
||||
isTooltipEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
isTabIndentationEnabled: false,
|
||||
isMultiCursorEnabled: false,
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
|
||||
@@ -21,11 +21,11 @@ const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||
const keymaps = {
|
||||
vim,
|
||||
emacs,
|
||||
codemirror: () => keymap.of(defaultKeymap),
|
||||
vscode: vscodeExtension,
|
||||
};
|
||||
|
||||
export function keybindings(name) {
|
||||
const active = keymaps[name];
|
||||
return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
|
||||
// keymap.of(searchKeymap),
|
||||
return [active ? active() : [], keymap.of(historyKeymap)];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { describe, bench } from 'vitest';
|
||||
|
||||
import { calculateTactus, sequence, stack } from '../index.mjs';
|
||||
import { calculateSteps, sequence, stack } from '../index.mjs';
|
||||
|
||||
const pat64 = sequence(...Array(64).keys());
|
||||
|
||||
describe('steps', () => {
|
||||
calculateTactus(true);
|
||||
calculateSteps(true);
|
||||
bench(
|
||||
'+tactus',
|
||||
() => {
|
||||
@@ -14,7 +14,7 @@ describe('steps', () => {
|
||||
{ time: 1000 },
|
||||
);
|
||||
|
||||
calculateTactus(false);
|
||||
calculateSteps(false);
|
||||
bench(
|
||||
'-tactus',
|
||||
() => {
|
||||
@@ -25,7 +25,7 @@ describe('steps', () => {
|
||||
});
|
||||
|
||||
describe('stack', () => {
|
||||
calculateTactus(true);
|
||||
calculateSteps(true);
|
||||
bench(
|
||||
'+tactus',
|
||||
() => {
|
||||
@@ -34,7 +34,7 @@ describe('stack', () => {
|
||||
{ time: 1000 },
|
||||
);
|
||||
|
||||
calculateTactus(false);
|
||||
calculateSteps(false);
|
||||
bench(
|
||||
'-tactus',
|
||||
() => {
|
||||
@@ -43,4 +43,4 @@ describe('stack', () => {
|
||||
{ time: 1000 },
|
||||
);
|
||||
});
|
||||
calculateTactus(true);
|
||||
calculateSteps(true);
|
||||
|
||||
+146
-13
@@ -252,6 +252,20 @@ export const { fmenv } = registerControl('fmenv');
|
||||
*
|
||||
*/
|
||||
export const { fmattack } = registerControl('fmattack');
|
||||
|
||||
/**
|
||||
* waveform of the fm modulator
|
||||
*
|
||||
* @name fmwave
|
||||
* @param {number | Pattern} wave waveform
|
||||
* @example
|
||||
* n("0 1 2 3".fast(4)).scale("d:minor").s("sine").fmwave("<sine square sawtooth crackle>").fm(4).fmh(2.01)
|
||||
* @example
|
||||
* n("0 1 2 3".fast(4)).chord("<Dm Am F G>").voicing().s("sawtooth").fmwave("brown").fm(.6)
|
||||
*
|
||||
*/
|
||||
export const { fmwave } = registerControl('fmwave');
|
||||
|
||||
/**
|
||||
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
||||
*
|
||||
@@ -306,6 +320,7 @@ export const { fft } = registerControl('fft');
|
||||
*
|
||||
* @name decay
|
||||
* @param {number | Pattern} time decay time in seconds
|
||||
* @synonyms dec
|
||||
* @example
|
||||
* note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||
*
|
||||
@@ -444,6 +459,78 @@ export const { crush } = registerControl('crush');
|
||||
*/
|
||||
export const { coarse } = registerControl('coarse');
|
||||
|
||||
/**
|
||||
* modulate the amplitude of a sound with a continuous waveform
|
||||
*
|
||||
* @name tremolo
|
||||
* @synonyms trem
|
||||
* @param {number | Pattern} speed modulation speed in HZ
|
||||
* @example
|
||||
* note("d d d# d".fast(4)).s("supersaw").tremolo("<3 2 100> ").tremoloskew("<.5>")
|
||||
*
|
||||
*/
|
||||
export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], 'trem');
|
||||
|
||||
/**
|
||||
* modulate the amplitude of a sound with a continuous waveform
|
||||
*
|
||||
* @name tremolosync
|
||||
* @synonyms tremsync
|
||||
* @param {number | Pattern} cycles modulation speed in cycles
|
||||
* @example
|
||||
* note("d d d# d".fast(4)).s("supersaw").tremolosync("4").tremoloskew("<1 .5 0>")
|
||||
*
|
||||
*/
|
||||
export const { tremolosync } = registerControl(
|
||||
['tremolosync', 'tremolodepth', 'tremoloskew', 'tremolophase'],
|
||||
'tremsync',
|
||||
);
|
||||
|
||||
/**
|
||||
* depth of amplitude modulation
|
||||
*
|
||||
* @name tremolodepth
|
||||
* @synonyms tremdepth
|
||||
* @param {number | Pattern} depth
|
||||
* @example
|
||||
* note("a1 a1 a#1 a1".fast(4)).s("pulse").tremsync(4).tremolodepth("<1 2 .7>")
|
||||
*
|
||||
*/
|
||||
export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth');
|
||||
/**
|
||||
* alter the shape of the modulation waveform
|
||||
*
|
||||
* @name tremoloskew
|
||||
* @synonyms tremskew
|
||||
* @param {number | Pattern} amount between 0 & 1, the shape of the waveform
|
||||
* @example
|
||||
* note("{f a c e}%16").s("sawtooth").tremsync(4).tremoloskew("<.5 0 1>")
|
||||
*
|
||||
*/
|
||||
export const { tremoloskew } = registerControl('tremoloskew', 'tremskew');
|
||||
|
||||
/**
|
||||
* alter the phase of the modulation waveform
|
||||
*
|
||||
* @name tremolophase
|
||||
* @synonyms tremphase
|
||||
* @param {number | Pattern} offset the offset in cycles of the modulation
|
||||
* @example
|
||||
* note("{f a c e}%16").s("sawtooth").tremsync(4).tremolophase("<0 .25 .66>")
|
||||
*
|
||||
*/
|
||||
export const { tremolophase } = registerControl('tremolophase', 'tremphase');
|
||||
|
||||
/**
|
||||
* shape of amplitude modulation
|
||||
*
|
||||
* @name tremoloshape
|
||||
* @param {number | Pattern} shape tri | square | sine | saw | ramp
|
||||
* @example
|
||||
* note("{f g c d}%16").tremsync(4).tremoloshape("<sine tri square>").s("sawtooth")
|
||||
*
|
||||
*/
|
||||
export const { tremoloshape } = registerControl('tremoloshape', 'tremshape');
|
||||
/**
|
||||
* filter overdrive for supported filter types
|
||||
*
|
||||
@@ -453,6 +540,42 @@ export const { coarse } = registerControl('coarse');
|
||||
* note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>")
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* modulate the amplitude of an orbit to create a "sidechain" like effect
|
||||
*
|
||||
* @name duckorbit
|
||||
* @param {number | Pattern} orbit target orbit
|
||||
* @example
|
||||
* $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
|
||||
* $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth(1)
|
||||
*
|
||||
*/
|
||||
export const { duck } = registerControl('duckorbit', 'duck');
|
||||
|
||||
/**
|
||||
* the amount of ducking applied to target orbit
|
||||
*
|
||||
* @name duckdepth
|
||||
* @param {number | Pattern} depth depth of modulation from 0 to 1
|
||||
* @example
|
||||
* stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>"))
|
||||
*
|
||||
*/
|
||||
|
||||
export const { duckdepth } = registerControl('duckdepth');
|
||||
|
||||
/**
|
||||
* the attack time of the duck effect
|
||||
*
|
||||
* @name duckattack
|
||||
* @param {number | Pattern} time
|
||||
* @example
|
||||
* stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack("<0.2 0 0.4>").duckdepth(1))
|
||||
*
|
||||
*/
|
||||
export const { duckattack } = registerControl('duckattack', 'duckatt');
|
||||
|
||||
export const { drive } = registerControl('drive');
|
||||
|
||||
/**
|
||||
@@ -1393,6 +1516,29 @@ export const { roomfade, rfade } = registerControl('roomfade', 'rfade');
|
||||
*
|
||||
*/
|
||||
export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse');
|
||||
|
||||
/**
|
||||
* Sets speed of the sample for the impulse response.
|
||||
* @name irspeed
|
||||
* @param {string | Pattern} speed
|
||||
* @example
|
||||
* samples('github:switchangel/pad')
|
||||
* $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.2).irspeed("<2 1 .5>/2").irbegin(.5).roomsize(.5)
|
||||
*
|
||||
*/
|
||||
export const { irspeed } = registerControl('irspeed');
|
||||
|
||||
/**
|
||||
* Sets the beginning of the IR response sample
|
||||
* @name irbegin
|
||||
* @param {string | Pattern} begin between 0 and 1
|
||||
* @synonyms ir
|
||||
* @example
|
||||
* samples('github:switchangel/pad')
|
||||
* $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.65).irspeed("-2").irbegin("<0 .5 .75>/2").roomsize(.6)
|
||||
*
|
||||
*/
|
||||
export const { irbegin } = registerControl('irbegin');
|
||||
/**
|
||||
* Sets the room size of the reverb, see `room`.
|
||||
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
|
||||
@@ -1561,18 +1707,6 @@ export const { density } = registerControl('density');
|
||||
// ['modwheel'],
|
||||
export const { expression } = registerControl('expression');
|
||||
export const { sustainpedal } = registerControl('sustainpedal');
|
||||
/* // TODO: doesn't seem to do anything
|
||||
*
|
||||
* Tremolo Audio DSP effect
|
||||
*
|
||||
* @name tremolodepth
|
||||
* @param {number | Pattern} depth between 0 and 1
|
||||
* @example
|
||||
* n("0,4,7").tremolodepth("<0 .3 .6 .9>").osc()
|
||||
*
|
||||
*/
|
||||
export const { tremolodepth, tremdp } = registerControl('tremolodepth', 'tremdp');
|
||||
export const { tremolorate, tremr } = registerControl('tremolorate', 'tremr');
|
||||
|
||||
export const { fshift } = registerControl('fshift');
|
||||
export const { fshiftnote } = registerControl('fshiftnote');
|
||||
@@ -1649,7 +1783,6 @@ export const { zmod } = registerControl('zmod');
|
||||
// like crush but scaled differently
|
||||
export const { zcrush } = registerControl('zcrush');
|
||||
export const { zdelay } = registerControl('zdelay');
|
||||
export const { tremolo } = registerControl('tremolo');
|
||||
export const { zzfx } = registerControl('zzfx');
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import createClock from './zyklus.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { errorLogger, logger } from './logger.mjs';
|
||||
|
||||
export class Cyclist {
|
||||
constructor({
|
||||
@@ -67,6 +67,7 @@ export class Cyclist {
|
||||
// the following line is dumb and only here for backwards compatibility
|
||||
// see https://codeberg.org/uzu/strudel/pulls/1004
|
||||
const deadline = targetTime - phase;
|
||||
// this onTrigger has another signature
|
||||
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
|
||||
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
|
||||
this.cps = hap.value.cps;
|
||||
@@ -75,7 +76,7 @@ export class Cyclist {
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger(`[cyclist] error: ${e.message}`);
|
||||
errorLogger(e);
|
||||
onError?.(e);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ 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 { timeCat, register, silence } from './pattern.mjs';
|
||||
import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs';
|
||||
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
|
||||
import Fraction, { lcm } from './fraction.mjs';
|
||||
|
||||
@@ -196,3 +196,26 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
|
||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||
return _euclidLegato(pulses, steps, rotation, pat);
|
||||
});
|
||||
|
||||
/**
|
||||
* A 'euclid' variant with an additional parameter that morphs the resulting
|
||||
* rhythm from 0 (no morphing) to 1 (completely 'even'). For example
|
||||
* `sound("bd").euclidish(3,8,0)` would be the same as
|
||||
* `sound("bd").euclid(3,8)`, and `sound("bd").euclidish(3,8,1)` would be the
|
||||
* same as `sound("bd bd bd")`. `sound("bd").euclidish(3,8,0.5)` would have a
|
||||
* groove somewhere between.
|
||||
* Inspired by the work of Malcom Braff.
|
||||
* @name euclidish
|
||||
* @synonyms eish
|
||||
* @memberof Pattern
|
||||
* @param {number} pulses the number of onsets
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse)
|
||||
* @example
|
||||
* sound("hh").euclidish(7,12,sine.slow(8))
|
||||
* .pan(sine.slow(8))
|
||||
*/
|
||||
export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) {
|
||||
const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc);
|
||||
return pat.struct(morphed).setSteps(steps);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
||||
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';
|
||||
import { stringifyValues } from './util.mjs';
|
||||
|
||||
export class Hap {
|
||||
/*
|
||||
@@ -148,13 +149,7 @@ export class Hap {
|
||||
}
|
||||
|
||||
showWhole(compact = false) {
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${
|
||||
typeof this.value === 'object'
|
||||
? compact
|
||||
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
||||
: JSON.stringify(this.value)
|
||||
: this.value
|
||||
}`;
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${stringifyValues(this.value, compact)}`;
|
||||
}
|
||||
|
||||
combineContext(b) {
|
||||
|
||||
@@ -4,6 +4,12 @@ let debounce = 1000,
|
||||
lastMessage,
|
||||
lastTime;
|
||||
|
||||
export function errorLogger(e, origin = 'cyclist') {
|
||||
//TODO: add some kind of debug flag that enables this while in dev mode
|
||||
// console.error(e);
|
||||
logger(`[${origin}] error: ${e.message}`);
|
||||
}
|
||||
|
||||
export function logger(message, type, data = {}) {
|
||||
let t = performance.now();
|
||||
if (lastMessage === message && t - lastTime < debounce) {
|
||||
|
||||
@@ -11,7 +11,6 @@ export class NeoCyclist {
|
||||
constructor({ onTrigger, onToggle, getTime }) {
|
||||
this.started = false;
|
||||
this.cps = 0.5;
|
||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||
this.getTime = getTime; // get absolute time
|
||||
this.time_at_last_tick_message = 0;
|
||||
// the clock of the worker and the audio context clock can drift apart over time
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/core",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
+96
-24
@@ -21,6 +21,8 @@ import {
|
||||
numeralArgs,
|
||||
parseNumeral,
|
||||
pairs,
|
||||
zipWith,
|
||||
stringifyValues,
|
||||
} from './util.mjs';
|
||||
import drawLine from './drawLine.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
@@ -46,11 +48,10 @@ export class Pattern {
|
||||
* @param {function} query - The function that maps a `State` to an array of `Hap`.
|
||||
* @noAutocomplete
|
||||
*/
|
||||
constructor(query, steps = undefined, alignment = undefined) {
|
||||
constructor(query, steps = undefined) {
|
||||
this.query = query;
|
||||
this._Pattern = true; // this property is used to detectinstance of another Pattern
|
||||
this._steps = steps; // in terms of number of steps per cycle
|
||||
this._alignment = alignment;
|
||||
}
|
||||
|
||||
get _steps() {
|
||||
@@ -77,11 +78,6 @@ export class Pattern {
|
||||
return this._steps !== undefined;
|
||||
}
|
||||
|
||||
setAlignment(alignment) {
|
||||
this._alignment = alignment;
|
||||
return this;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Haskell-style functor, applicative and monadic operations
|
||||
|
||||
@@ -858,14 +854,29 @@ export class Pattern {
|
||||
);
|
||||
}
|
||||
|
||||
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) {
|
||||
/**
|
||||
* Writes the content of the current event to the console (visible in the side menu).
|
||||
* @name log
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* s("bd sd").log()
|
||||
*/
|
||||
log(func = (hap) => `[hap] ${hap.showWhole(true)}`, getData = (hap) => ({ hap })) {
|
||||
return this.onTrigger((...args) => {
|
||||
logger(func(...args), undefined, getData(...args));
|
||||
}, false);
|
||||
}
|
||||
|
||||
logValues(func = id) {
|
||||
return this.log((_, hap) => func(hap.value));
|
||||
/**
|
||||
* A simplified version of `log` which writes all "values" (various configurable parameters)
|
||||
* within the event to the console (visible in the side menu).
|
||||
* @name logValues
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* s("bd sd").gain("0.25 0.5 1").n("2 1 0").logValues()
|
||||
*/
|
||||
logValues(func = (value) => `[hap] ${stringifyValues(value, true)}`) {
|
||||
return this.log((hap) => func(hap.value));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -1294,7 +1305,7 @@ export function sequenceP(pats) {
|
||||
* @synonyms polyrhythm, pr
|
||||
* @example
|
||||
* stack("g3", "b3", ["e4", "d4"]).note()
|
||||
* // "g3,b3,[e4,d4]".note()
|
||||
* // "g3,b3,[e4 d4]".note()
|
||||
*
|
||||
* @example
|
||||
* // As a chained function:
|
||||
@@ -1575,7 +1586,7 @@ export const func = curry((a, b) => reify(b).func(a));
|
||||
/**
|
||||
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
|
||||
*
|
||||
* @param {string} name name of the function
|
||||
* @param {string | string[]} name name of the function, or an array of names to be used as synonyms
|
||||
* @param {function} func function with 1 or more params, where last is the current pattern
|
||||
* @noAutocomplete
|
||||
*
|
||||
@@ -1616,13 +1627,8 @@ export function register(name, func, patternify = true, preserveSteps = false, j
|
||||
let mapFn = (...args) => {
|
||||
return func(...args, pat);
|
||||
};
|
||||
// mapFn = curry(mapFn, null, arity - 1);
|
||||
// result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)));
|
||||
// patternify2 f pata patb patc = pata >>= \a -> patb >>= \b -> f a b patc
|
||||
function bindArgs(argv, pat, ...pats) {
|
||||
return pat.innerBind((x) => (pats.length ? bindArgs([...argv, x], ...pats) : mapFn(...argv, x)));
|
||||
}
|
||||
result = bindArgs([], ...firstArgs);
|
||||
mapFn = curry(mapFn, null, arity - 1);
|
||||
result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)));
|
||||
}
|
||||
}
|
||||
if (preserveSteps) {
|
||||
@@ -2533,7 +2539,7 @@ export const { fastchunk, fastChunk } = register(
|
||||
/**
|
||||
* Like `chunk`, but the function is applied to a looped subcycle of the source pattern.
|
||||
* @name chunkInto
|
||||
* @synonym chunkinto
|
||||
* @synonyms chunkinto
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
|
||||
@@ -2546,7 +2552,7 @@ export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], fun
|
||||
/**
|
||||
* Like `chunkInto`, but moves backwards through the chunks.
|
||||
* @name chunkBackInto
|
||||
* @synonym chunkbackinto
|
||||
* @synonyms chunkbackinto
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
|
||||
@@ -2576,7 +2582,7 @@ export const bypass = register(
|
||||
* Loops the pattern inside an `offset` for `cycles`.
|
||||
* If you think of the entire span of time in cycles as a ribbon, you can cut a single piece and loop it.
|
||||
* @name ribbon
|
||||
* @synonym rib
|
||||
* @synonyms rib
|
||||
* @param {number} offset start point of loop in cycles
|
||||
* @param {number} cycles loop length in cycles
|
||||
* @example
|
||||
@@ -3271,10 +3277,10 @@ export const slice = register(
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd!8").onTriggerTime((hap) => {console.info(hap)})
|
||||
* s("bd!8").onTriggerTime((hap) => {console.log(hap)})
|
||||
*/
|
||||
Pattern.prototype.onTriggerTime = function (func) {
|
||||
return this.onTrigger((t_deprecate, hap, currentTime, cps = 1, targetTime) => {
|
||||
return this.onTrigger((hap, currentTime, _cps, targetTime) => {
|
||||
const diff = targetTime - currentTime;
|
||||
window.setTimeout(() => {
|
||||
func(hap);
|
||||
@@ -3411,3 +3417,69 @@ export const { beat } = register(
|
||||
['beat'],
|
||||
__beat((x) => x.innerJoin()),
|
||||
);
|
||||
|
||||
export const _morph = (from, to, by) => {
|
||||
by = Fraction(by);
|
||||
const dur = Fraction(1).div(from.length);
|
||||
const positions = (list) => {
|
||||
const result = [];
|
||||
for (const [pos, value] of list.entries()) {
|
||||
if (value) {
|
||||
result.push([Fraction(pos).div(list.length), value]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const arcs = zipWith(
|
||||
([posa, valuea], [posb, valueb]) => {
|
||||
const b = by.mul(posb - posa).add(posa);
|
||||
const e = b.add(dur);
|
||||
return new TimeSpan(b, e);
|
||||
},
|
||||
positions(from),
|
||||
positions(to),
|
||||
);
|
||||
function query(state) {
|
||||
const cycle = state.span.begin.sam();
|
||||
const cycleArc = state.span.cycleArc();
|
||||
const result = [];
|
||||
for (const whole of arcs) {
|
||||
const part = whole.intersection(cycleArc);
|
||||
if (part !== undefined) {
|
||||
result.push(
|
||||
new Hap(
|
||||
whole.withTime((x) => x.add(cycle)),
|
||||
part.withTime((x) => x.add(cycle)),
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new Pattern(query).splitQueries();
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes two binary rhythms represented as lists of 1s and 0s, and a number
|
||||
* between 0 and 1 that morphs between them. The two lists should contain the same
|
||||
* number of true values.
|
||||
* @example
|
||||
* sound("hh").struct(morph([1,0,1,0,1,0,1,0], // straight rhythm
|
||||
* [1,1,0,1,0,1,0], // wonky rhythm
|
||||
* 0.25 // creates a slightly wonky rhythm
|
||||
* )
|
||||
* )
|
||||
* @example
|
||||
* sound("hh").struct(morph("1:0:1:0:1:0:1:0", // straight rhythm
|
||||
* "1:1:0:1:0:1:0", // wonky rhythm
|
||||
* sine.slow(8) // slowly morph between the rhythms
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
export const morph = (frompat, topat, bypat) => {
|
||||
frompat = reify(frompat);
|
||||
topat = reify(topat);
|
||||
bypat = reify(bypat);
|
||||
return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NeoCyclist } from './neocyclist.mjs';
|
||||
import { Cyclist } from './cyclist.mjs';
|
||||
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { errorLogger, logger } from './logger.mjs';
|
||||
import { setTime } from './time.mjs';
|
||||
import { evalScope } from './evaluate.mjs';
|
||||
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
|
||||
@@ -245,6 +245,7 @@ export function repl({
|
||||
export const getTrigger =
|
||||
({ getTime, defaultOutput }) =>
|
||||
async (hap, deadline, duration, cps, t) => {
|
||||
// ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
|
||||
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
||||
try {
|
||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||
@@ -252,9 +253,9 @@ export const getTrigger =
|
||||
}
|
||||
if (hap.context.onTrigger) {
|
||||
// call signature of output / onTrigger is different...
|
||||
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps, t);
|
||||
await hap.context.onTrigger(hap, getTime(), cps, t);
|
||||
}
|
||||
} catch (err) {
|
||||
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||
errorLogger(err, 'getTrigger');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -264,7 +264,7 @@ export const randrun = (n) => {
|
||||
const rands = timeToRands(t.floor().add(0.5), n);
|
||||
const nums = rands
|
||||
.map((n, i) => [n, i])
|
||||
.sort((a, b) => a[0] > b[0] - a[0] < b[0])
|
||||
.sort((a, b) => (a[0] > b[0]) - (a[0] < b[0]))
|
||||
.map((x) => x[1]);
|
||||
const i = t.cyclePos().mul(n).floor() % n;
|
||||
return nums[i];
|
||||
|
||||
@@ -32,7 +32,7 @@ function triggerSpeech(words, lang, voice) {
|
||||
}
|
||||
|
||||
export const speak = register('speak', function (lang, voice, pat) {
|
||||
return pat.onTrigger((_, hap) => {
|
||||
return pat.onTrigger((hap) => {
|
||||
triggerSpeech(hap.value, lang, voice);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import Fraction from 'fraction.js';
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
TimeSpan,
|
||||
@@ -55,6 +55,8 @@ import {
|
||||
expand,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { log, logValues } from '../pattern.mjs';
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
|
||||
import { n, s } from '../controls.mjs';
|
||||
@@ -1306,4 +1308,40 @@ describe('Pattern', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('log', () => {
|
||||
it('logs to console', () => {
|
||||
const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const pattern = pure('a').log();
|
||||
const haps = pattern.queryArc(0, 1);
|
||||
|
||||
// Force a trigger
|
||||
haps.forEach((hap) => {
|
||||
hap.context?.onTrigger?.(hap);
|
||||
});
|
||||
|
||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||
'%c[hap] 0/1 → 1/1: a',
|
||||
'background-color: black;color:white;border-radius:15px',
|
||||
);
|
||||
mockConsoleLog.mockRestore();
|
||||
});
|
||||
});
|
||||
describe('logValues', () => {
|
||||
it('logs values to console', () => {
|
||||
const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const pattern = pure('a').note('c#').logValues();
|
||||
const haps = pattern.queryArc(0, 1);
|
||||
|
||||
// Force a trigger
|
||||
haps.forEach((hap) => {
|
||||
hap.context?.onTrigger?.(hap);
|
||||
});
|
||||
|
||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||
'%c[hap] value:a note:c#',
|
||||
'background-color: black;color:white;border-radius:15px',
|
||||
);
|
||||
mockConsoleLog.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ export class TimeSpan {
|
||||
}
|
||||
|
||||
intersection(other) {
|
||||
// Intersection of two timespans, returns None if they don't intersect.
|
||||
// Intersection of two timespans, returns undefined if they don't intersect.
|
||||
const intersect_begin = this.begin.max(other.begin);
|
||||
const intersect_end = this.end.min(other.end);
|
||||
|
||||
|
||||
@@ -487,3 +487,13 @@ export function getCurrentKeyboardState() {
|
||||
// }
|
||||
// return lcm((x * y) / gcd(x, y), ...z);
|
||||
// };
|
||||
|
||||
// Takes values -- typically derived from events, i.e. `hap`s -- and renders them
|
||||
// into a readable format
|
||||
export function stringifyValues(value, compact = false) {
|
||||
return typeof value === 'object'
|
||||
? compact
|
||||
? JSON.stringify(value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
||||
: JSON.stringify(value)
|
||||
: value;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export const csound = register('csound', (instrument, pat) => {
|
||||
instrument = instrument || 'triangle';
|
||||
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
|
||||
// TODO: find a alternative way to wait for csound to load (to wait with first time playback)
|
||||
return pat.onTrigger((time_deprecate, hap, currentTime, _cps, targetTime) => {
|
||||
return pat.onTrigger((hap, currentTime, _cps, targetTime) => {
|
||||
if (!_csound) {
|
||||
logger('[csound] not loaded yet', 'warning');
|
||||
return;
|
||||
@@ -142,7 +142,7 @@ export const csoundm = register('csoundm', (instrument, pat) => {
|
||||
p1 = `"${instrument}"`;
|
||||
}
|
||||
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
|
||||
return pat.onTrigger((tidal_time, hap) => {
|
||||
return pat.onTrigger((hap, currentTime, _cps, targetTime) => {
|
||||
if (!_csound) {
|
||||
logger('[csound] not loaded yet', 'warning');
|
||||
return;
|
||||
@@ -151,7 +151,7 @@ export const csoundm = register('csoundm', (instrument, pat) => {
|
||||
throw new Error('csound only support objects as hap values');
|
||||
}
|
||||
// Time in seconds counting from now.
|
||||
const p2 = tidal_time - getAudioContext().currentTime;
|
||||
const p2 = targetTime - currentTime;
|
||||
const p3 = hap.duration.valueOf() + 0;
|
||||
const frequency = getFrequency(hap);
|
||||
let { gain = 1, velocity = 0.9 } = hap.value;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/csound",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -6,7 +6,7 @@ const OFF_MESSAGE = 0x80;
|
||||
const CC_MESSAGE = 0xb0;
|
||||
|
||||
Pattern.prototype.midi = function (output) {
|
||||
return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
|
||||
return this.onTrigger((hap, currentTime, cps, targetTime) => {
|
||||
let { note, nrpnn, nrpv, ccn, ccv, velocity = 0.9, gain = 1 } = hap.value;
|
||||
//magic number to get audio engine to line up, can probably be calculated somehow
|
||||
const latencyMs = 34;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Invoke } from './utils.mjs';
|
||||
|
||||
const collator = new ClockCollator({});
|
||||
|
||||
export async function oscTriggerTauri(t_deprecate, hap, currentTime, cps = 1, targetTime) {
|
||||
export async function oscTriggerTauri(hap, currentTime, cps = 1, targetTime) {
|
||||
const controls = parseControlsFromHap(hap, cps);
|
||||
const params = [];
|
||||
const timestamp = collator.calculateTimestamp(currentTime, targetTime);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/draw",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Helpers for drawing with Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/embed",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
||||
"main": "embed.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/gamepad",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Gamepad Inputs for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/hydra",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Hydra integration for strudel",
|
||||
"main": "hydra.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -333,7 +333,7 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
||||
logger(`Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`),
|
||||
});
|
||||
|
||||
return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
|
||||
return this.onTrigger((hap, currentTime, cps, targetTime) => {
|
||||
if (!WebMidi.enabled) {
|
||||
logger('Midi not enabled');
|
||||
return;
|
||||
@@ -493,6 +493,9 @@ export async function midin(input) {
|
||||
otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : ''
|
||||
}`,
|
||||
);
|
||||
}
|
||||
// ensure refs for this input are initialized
|
||||
if (!refs[input]) {
|
||||
refs[input] = {};
|
||||
}
|
||||
const cc = (cc) => ref(() => refs[input][cc] || 0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/midi",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, bench } from 'vitest';
|
||||
|
||||
import { calculateTactus } from '../../core/index.mjs';
|
||||
import { calculateSteps } from '../../core/index.mjs';
|
||||
import { mini } from '../index.mjs';
|
||||
|
||||
describe('mini', () => {
|
||||
calculateTactus(true);
|
||||
calculateSteps(true);
|
||||
bench(
|
||||
'+tactus',
|
||||
() => {
|
||||
@@ -13,7 +13,7 @@ describe('mini', () => {
|
||||
{ time: 1000 },
|
||||
);
|
||||
|
||||
calculateTactus(false);
|
||||
calculateSteps(false);
|
||||
bench(
|
||||
'-tactus',
|
||||
() => {
|
||||
@@ -21,5 +21,5 @@ describe('mini', () => {
|
||||
},
|
||||
{ time: 1000 },
|
||||
);
|
||||
calculateTactus(true);
|
||||
calculateSteps(true);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mini",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mondolang",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "a language for functional composition that translates to js",
|
||||
"main": "mondo.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -42,6 +42,7 @@ lib['%'] = pace;
|
||||
lib['?'] = degradeBy; // todo: default 0.5 not working..
|
||||
lib[':'] = tail;
|
||||
lib['..'] = range;
|
||||
lib['def'] = () => silence;
|
||||
lib['or'] = (...children) => chooseIn(...children); // always has structure but is cyclewise.. e.g. "s oh*8.dec[.04 | .5]"
|
||||
//lib['or'] = (...children) => chooseOut(...children); // "s oh*8.dec[.04 | .5]" is better but "dec[.04 | .5].s oh*8" has no struct
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mondo",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.2",
|
||||
"description": "mondo notation for strudel",
|
||||
"main": "mondough.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/motion",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "DeviceMotion API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mqtt",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "MQTT API for strudel",
|
||||
"main": "mqtt.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -60,7 +60,7 @@ export function parseControlsFromHap(hap, cps) {
|
||||
|
||||
const collator = new ClockCollator({});
|
||||
|
||||
export async function oscTrigger(t_deprecate, hap, currentTime, cps = 1, targetTime) {
|
||||
export async function oscTrigger(hap, currentTime, cps = 1, targetTime) {
|
||||
const osc = await connect();
|
||||
const controls = parseControlsFromHap(hap, cps);
|
||||
const keyvals = Object.entries(controls).flat();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/osc",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "OSC messaging for strudel",
|
||||
"main": "osc.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { oscTriggerTauri } from '../desktopbridge/oscbridge.mjs';
|
||||
import { isTauri } from '../desktopbridge/utils.mjs';
|
||||
/* import { oscTriggerTauri } from '../desktopbridge/oscbridge.mjs';
|
||||
import { isTauri } from '../desktopbridge/utils.mjs'; */
|
||||
import { oscTrigger } from './osc.mjs';
|
||||
|
||||
const trigger = isTauri() ? oscTriggerTauri : oscTrigger;
|
||||
const trigger = /* isTauri() ? oscTriggerTauri : */ oscTrigger;
|
||||
|
||||
export const superdirtOutput = (hap, deadline, hapDuration, cps, targetTime) => {
|
||||
const currentTime = performance.now() / 1000;
|
||||
return trigger(null, hap, currentTime, cps, targetTime);
|
||||
return trigger(hap, currentTime, cps, targetTime);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/reference",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"description": "Headless reference of all strudel functions",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/repl",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"description": "Strudel REPL as a Web Component",
|
||||
"module": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function prebake() {
|
||||
samples(`${ds}/tidal-drum-machines.json`),
|
||||
samples(`${ds}/piano.json`),
|
||||
samples(`${ds}/Dirt-Samples.json`),
|
||||
samples(`${ds}/EmuSP12.json`),
|
||||
samples(`${ds}/uzu-drumkit.json`),
|
||||
samples(`${ds}/vcsl.json`),
|
||||
samples(`${ds}/mridangam.json`),
|
||||
]);
|
||||
|
||||
@@ -20,3 +20,13 @@ samples('http://localhost:5432')
|
||||
LOG=1 npx @strudel/sampler # adds logging
|
||||
PORT=5555 npx @strudel/sampler # changes port
|
||||
```
|
||||
|
||||
## static json
|
||||
|
||||
when running with `--json`, you will simply get the json logged back:
|
||||
|
||||
```sh
|
||||
npx --yes @strudel/sampler --json > strudel.json
|
||||
```
|
||||
|
||||
this is useful if you want to create a sample pack from the current folder.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/sampler",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.3",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import cowsay from 'cowsay';
|
||||
import { createReadStream, existsSync } from 'fs';
|
||||
import { createReadStream, existsSync, writeFileSync } from 'fs';
|
||||
import { readdir } from 'fs/promises';
|
||||
import http from 'http';
|
||||
import { join, sep } from 'path';
|
||||
import { join, resolve, sep } from 'path';
|
||||
import readline from 'readline';
|
||||
import os from 'os';
|
||||
|
||||
// eslint-disable-next-line
|
||||
const LOG = !!process.env.LOG || false;
|
||||
const VALID_AUDIO_EXTENSIONS = ['wav', 'mp3', 'ogg'];
|
||||
|
||||
console.log(
|
||||
cowsay.say({
|
||||
text: 'welcome to @strudel/sampler',
|
||||
e: 'oO',
|
||||
T: 'U ',
|
||||
}),
|
||||
);
|
||||
const isAudioFile = (f) => {
|
||||
const ext = f.split('.').slice(-1)[0].toLowerCase();
|
||||
return VALID_AUDIO_EXTENSIONS.includes(ext);
|
||||
};
|
||||
|
||||
async function getFilesInDirectory(directory) {
|
||||
let files = [];
|
||||
@@ -29,42 +27,90 @@ async function getFilesInDirectory(directory) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const subFiles = (await getFilesInDirectory(fullPath)).filter((f) =>
|
||||
['wav', 'mp3', 'ogg'].includes(f.split('.').slice(-1)[0].toLowerCase()),
|
||||
);
|
||||
const subFiles = (await getFilesInDirectory(fullPath)).filter(isAudioFile);
|
||||
files = files.concat(subFiles);
|
||||
LOG && console.log(`${dirent.name} (${subFiles.length})`);
|
||||
} catch (err) {
|
||||
LOG && console.warn(`skipped due to error: ${fullPath}`);
|
||||
}
|
||||
} else {
|
||||
files.push(fullPath);
|
||||
isAudioFile(fullPath) && files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
async function getBanks(directory) {
|
||||
async function getBanks(directory, flat = false) {
|
||||
let files = await getFilesInDirectory(directory);
|
||||
let banks = {};
|
||||
directory = directory.split(sep).join('/');
|
||||
files = files.map((path) => {
|
||||
path = path.split(sep).join('/');
|
||||
const [bank] = path.split('/').slice(-2);
|
||||
const subDir = path.replace(directory, '');
|
||||
const subDirFlat = subDir.replaceAll('/', '_').slice(1); // remove initial underscore
|
||||
const subDirFlatStem = subDirFlat.replace(/\.[^.]+$/, ''); // remove extension
|
||||
let bank = flat ? subDirFlatStem : path.split('/').slice(-2)[0];
|
||||
banks[bank] = banks[bank] || [];
|
||||
const relativeUrl = path.replace(directory, '');
|
||||
banks[bank].push(relativeUrl);
|
||||
return relativeUrl;
|
||||
banks[bank].push(subDir);
|
||||
return subDir;
|
||||
});
|
||||
banks._base = `http://localhost:5432`;
|
||||
return { banks, files };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
const directory = process.cwd();
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
function getArgValue(flag) {
|
||||
const i = args.indexOf(flag);
|
||||
if (i !== -1) {
|
||||
const nextIsFlag = args[i + 1]?.startsWith('--') ?? true;
|
||||
if (nextIsFlag) return true;
|
||||
return args[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
function getInput(query) {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
return new Promise((resolve) =>
|
||||
rl.question(query, (response) => {
|
||||
rl.close();
|
||||
resolve(response);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let directory = getArgValue('--dir') || process.cwd();
|
||||
directory = resolve(directory);
|
||||
if (args.includes('--json')) {
|
||||
const { banks } = await getBanks(directory, getArgValue('--flat'));
|
||||
const json = JSON.stringify(banks);
|
||||
const outFile = resolve(directory, 'strudel.json');
|
||||
if (existsSync(outFile)) {
|
||||
const answer = await getInput(`Warning: File already exists at ${outFile}. Overwrite? (y/N): `);
|
||||
if (answer.toLowerCase() !== 'y') {
|
||||
console.log('Aborted.');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
writeFileSync(outFile, json, 'utf8');
|
||||
console.log(`Wrote json to ${outFile}`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
cowsay.say({
|
||||
text: 'welcome to @strudel/sampler',
|
||||
e: 'oO',
|
||||
T: 'U ',
|
||||
}),
|
||||
);
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const { banks, files } = await getBanks(directory);
|
||||
const { banks, files } = await getBanks(directory, getArgValue('--flat'));
|
||||
if (req.url === '/') {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.end(JSON.stringify(banks));
|
||||
@@ -72,7 +118,7 @@ const server = http.createServer(async (req, res) => {
|
||||
let subpath = decodeURIComponent(req.url);
|
||||
const filePath = join(directory, subpath.split('/').join(sep));
|
||||
|
||||
//console.log('GET:', filePath);
|
||||
// console.log('GET:', filePath);
|
||||
const isFound = existsSync(filePath);
|
||||
if (!isFound) {
|
||||
res.statusCode = 404;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/serial",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Webserial API for strudel",
|
||||
"main": "serial.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -537,7 +537,7 @@ export default {
|
||||
],
|
||||
gm_synth_bass_1: [
|
||||
// Synth Bass 1: Bass
|
||||
'0380_Aspirin_sf2_file',
|
||||
// '0380_Aspirin_sf2_file', // broken in safari https://codeberg.org/uzu/strudel/issues/1384
|
||||
'0380_Chaos_sf2_file',
|
||||
'0380_FluidR3_GM_sf2_file',
|
||||
// 0380_GeneralUserGS_sf2_file // laut
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/soundfonts",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"description": "Soundsfont support for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getAudioContext, registerSound } from '@strudel/webaudio';
|
||||
import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
|
||||
|
||||
Pattern.prototype.soundfont = function (sf, n = 0) {
|
||||
return this.onTrigger((time_deprecate, h, ct, cps, targetTime) => {
|
||||
return this.onTrigger((h, ct, cps, targetTime) => {
|
||||
const ctx = getAudioContext();
|
||||
const note = getPlayableNoteValue(h);
|
||||
const preset = sf.presets[n % sf.presets.length];
|
||||
|
||||
@@ -74,6 +74,6 @@ export const dough = async (code) => {
|
||||
worklet.node.connect(ac.destination);
|
||||
};
|
||||
|
||||
export function doughTrigger(time_deprecate, hap, currentTime, cps, targetTime) {
|
||||
export function doughTrigger(hap, currentTime, cps, targetTime) {
|
||||
window.postMessage({ time: targetTime, dough: hap.value, currentTime, duration: hap.duration, cps });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { getAudioContext } from './superdough.mjs';
|
||||
import { clamp, nanFallback } from './util.mjs';
|
||||
import { getNoiseBuffer } from './noise.mjs';
|
||||
|
||||
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||
|
||||
export function gainNode(value) {
|
||||
const node = getAudioContext().createGain();
|
||||
@@ -206,19 +209,46 @@ export function getVibratoOscillator(param, value, t) {
|
||||
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
||||
// a bit of a hack, but it works very well :)
|
||||
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||
const constantNode = audioContext.createConstantSource();
|
||||
constantNode.start(startTime);
|
||||
constantNode.stop(stopTime);
|
||||
const constantNode = new ConstantSourceNode(audioContext);
|
||||
|
||||
// Certain browsers requires audio nodes to be connected in order for their onended events
|
||||
// to fire, so we _mute it_ and then connect it to the destination
|
||||
const zeroGain = gainNode(0);
|
||||
zeroGain.connect(audioContext.destination);
|
||||
constantNode.connect(zeroGain);
|
||||
|
||||
// Schedule the `onComplete` callback to occur at `stopTime`
|
||||
constantNode.onended = () => {
|
||||
// Ensure garbage collection
|
||||
try {
|
||||
zeroGain.disconnect();
|
||||
} catch {
|
||||
// pass
|
||||
}
|
||||
try {
|
||||
constantNode.disconnect();
|
||||
} catch {
|
||||
// pass
|
||||
}
|
||||
onComplete();
|
||||
};
|
||||
constantNode.start(startTime);
|
||||
constantNode.stop(stopTime);
|
||||
return constantNode;
|
||||
}
|
||||
const mod = (freq, range = 1, type = 'sine') => {
|
||||
const ctx = getAudioContext();
|
||||
const osc = ctx.createOscillator();
|
||||
osc.type = type;
|
||||
osc.frequency.value = freq;
|
||||
let osc;
|
||||
if (noises.includes(type)) {
|
||||
osc = ctx.createBufferSource();
|
||||
osc.buffer = getNoiseBuffer(type, 2);
|
||||
osc.loop = true;
|
||||
} else {
|
||||
osc = ctx.createOscillator();
|
||||
osc.type = type;
|
||||
osc.frequency.value = freq;
|
||||
}
|
||||
|
||||
osc.start();
|
||||
const g = new GainNode(ctx, { gain: range });
|
||||
osc.connect(g); // -range, range
|
||||
@@ -253,7 +283,7 @@ export function applyFM(param, value, begin) {
|
||||
|
||||
modulator = fmmod.node;
|
||||
stop = fmmod.stop;
|
||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
|
||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].some((v) => v !== undefined)) {
|
||||
// no envelope by default
|
||||
modulator.connect(param);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
let log = (msg) => console.log(msg);
|
||||
|
||||
export function errorLogger(e, origin = 'cyclist') {
|
||||
//TODO: add some kind of debug flag that enables this while in dev mode
|
||||
// console.error(e);
|
||||
logger(`[${origin}] error: ${e.message}`);
|
||||
}
|
||||
|
||||
export const logger = (...args) => log(...args);
|
||||
|
||||
export const setLogger = (fn) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getAudioContext } from './superdough.mjs';
|
||||
let noiseCache = {};
|
||||
|
||||
// lazy generates noise buffers and keeps them forever
|
||||
function getNoiseBuffer(type, density) {
|
||||
export function getNoiseBuffer(type, density) {
|
||||
const ac = getAudioContext();
|
||||
if (noiseCache[type]) {
|
||||
return noiseCache[type];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superdough",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import reverbGen from './reverbGen.mjs';
|
||||
import { clamp } from './util.mjs';
|
||||
|
||||
if (typeof AudioContext !== 'undefined') {
|
||||
AudioContext.prototype.adjustLength = function (duration, buffer) {
|
||||
AudioContext.prototype.adjustLength = function (duration, buffer, speed = 1, offsetAmount = 0) {
|
||||
const sampleOffset = Math.floor(clamp(offsetAmount, 0, 1) * buffer.length);
|
||||
const newLength = buffer.sampleRate * duration;
|
||||
const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
|
||||
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||
@@ -9,22 +11,30 @@ if (typeof AudioContext !== 'undefined') {
|
||||
let newData = newBuffer.getChannelData(channel);
|
||||
|
||||
for (let i = 0; i < newLength; i++) {
|
||||
newData[i] = oldData[i] || 0;
|
||||
// loop the buffer around to prevent
|
||||
let position = (sampleOffset + i * Math.abs(speed)) % oldData.length;
|
||||
if (speed < 1) {
|
||||
position = position * -1;
|
||||
}
|
||||
|
||||
newData[i] = oldData.at(position) || 0;
|
||||
}
|
||||
}
|
||||
return newBuffer;
|
||||
};
|
||||
|
||||
AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir) {
|
||||
AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir, irspeed, irbegin) {
|
||||
const convolver = this.createConvolver();
|
||||
convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, ir) => {
|
||||
convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, ir, irspeed, irbegin) => {
|
||||
convolver.duration = d;
|
||||
convolver.fade = fade;
|
||||
convolver.lp = lp;
|
||||
convolver.dim = dim;
|
||||
convolver.ir = ir;
|
||||
convolver.irspeed = irspeed;
|
||||
convolver.irbegin = irbegin;
|
||||
if (ir) {
|
||||
convolver.buffer = this.adjustLength(d, ir);
|
||||
convolver.buffer = this.adjustLength(d, ir, irspeed, irbegin);
|
||||
} else {
|
||||
reverbGen.generateReverb(
|
||||
{
|
||||
@@ -41,7 +51,7 @@ if (typeof AudioContext !== 'undefined') {
|
||||
);
|
||||
}
|
||||
};
|
||||
convolver.generate(duration, fade, lp, dim, ir);
|
||||
convolver.generate(duration, fade, lp, dim, ir, irspeed, irbegin);
|
||||
return convolver;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
import './feedbackdelay.mjs';
|
||||
import './reverb.mjs';
|
||||
import './vowel.mjs';
|
||||
import { clamp, nanFallback, _mod, cycleToSeconds } from './util.mjs';
|
||||
import { clamp, nanFallback, _mod, cycleToSeconds, secondsToCycle } from './util.mjs';
|
||||
import workletsUrl from './worklets.mjs?audioworklet';
|
||||
import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs';
|
||||
import { createFilter, gainNode, getCompressor, getWorklet, webAudioTimeout } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
import { logger, errorLogger } from './logger.mjs';
|
||||
import { loadBuffer } from './sampler.mjs';
|
||||
|
||||
export const DEFAULT_MAX_POLYPHONY = 128;
|
||||
@@ -28,6 +28,13 @@ export function setMultiChannelOrbits(bool) {
|
||||
multiChannelOrbits = bool == true;
|
||||
}
|
||||
|
||||
function getModulationShapeInput(val) {
|
||||
if (typeof val === 'number') {
|
||||
return val % 5;
|
||||
}
|
||||
return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0;
|
||||
}
|
||||
|
||||
export const soundMap = map();
|
||||
|
||||
export function registerSound(key, onTrigger, data = {}) {
|
||||
@@ -126,7 +133,7 @@ export const getAudioDevices = async () => {
|
||||
return devicesMap;
|
||||
};
|
||||
|
||||
const defaultDefaultValues = {
|
||||
let defaultDefaultValues = {
|
||||
s: 'triangle',
|
||||
gain: 0.8,
|
||||
postgain: 1,
|
||||
@@ -150,6 +157,17 @@ const defaultDefaultValues = {
|
||||
fft: 8,
|
||||
};
|
||||
|
||||
const defaultDefaultDefaultValues = Object.freeze({ ...defaultDefaultValues });
|
||||
|
||||
export function setDefault(control, value) {
|
||||
// const main = getControlName(control); // we cant do this because superdough is independent of strudel/core
|
||||
defaultDefaultValues[control] = value;
|
||||
}
|
||||
|
||||
export function resetDefaults() {
|
||||
defaultDefaultValues = { ...defaultDefaultDefaultValues };
|
||||
}
|
||||
|
||||
let defaultControls = new Map(Object.entries(defaultDefaultValues));
|
||||
|
||||
export function setDefaultValue(key, value) {
|
||||
@@ -264,7 +282,6 @@ export async function initAudioOnFirstClick(options) {
|
||||
return audioReady;
|
||||
}
|
||||
|
||||
let delays = {};
|
||||
const maxfeedback = 0.98;
|
||||
|
||||
let channelMerger, destinationGain;
|
||||
@@ -308,35 +325,45 @@ export const panic = () => {
|
||||
channelMerger == null;
|
||||
};
|
||||
|
||||
function getDelay(orbit, delaytime, delayfeedback, t, channels) {
|
||||
function getDelay(orbit, delaytime, delayfeedback, t) {
|
||||
if (delayfeedback > maxfeedback) {
|
||||
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
||||
}
|
||||
delayfeedback = clamp(delayfeedback, 0, 0.98);
|
||||
if (!delays[orbit]) {
|
||||
if (!orbits[orbit].delayNode) {
|
||||
const ac = getAudioContext();
|
||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||
dly.start?.(t); // for some reason, this throws when audion extension is installed..
|
||||
connectToDestination(dly, channels);
|
||||
delays[orbit] = dly;
|
||||
connectToOrbit(dly, orbit);
|
||||
orbits[orbit].delayNode = dly;
|
||||
}
|
||||
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
|
||||
delays[orbit].feedback.value !== delayfeedback && delays[orbit].feedback.setValueAtTime(delayfeedback, t);
|
||||
return delays[orbit];
|
||||
orbits[orbit].delayNode.delayTime.value !== delaytime &&
|
||||
orbits[orbit].delayNode.delayTime.setValueAtTime(delaytime, t);
|
||||
orbits[orbit].delayNode.feedback.value !== delayfeedback &&
|
||||
orbits[orbit].delayNode.feedback.setValueAtTime(delayfeedback, t);
|
||||
return orbits[orbit].delayNode;
|
||||
}
|
||||
|
||||
export function getLfo(audioContext, time, end, properties = {}) {
|
||||
return getWorklet(audioContext, 'lfo-processor', {
|
||||
export function getLfo(audioContext, begin, end, properties = {}) {
|
||||
const { shape = 0, ...props } = properties;
|
||||
const { dcoffset = -0.5, depth = 1 } = properties;
|
||||
const lfoprops = {
|
||||
frequency: 1,
|
||||
depth: 1,
|
||||
skew: 0,
|
||||
depth,
|
||||
skew: 0.5,
|
||||
phaseoffset: 0,
|
||||
time,
|
||||
time: begin,
|
||||
begin,
|
||||
end,
|
||||
shape: 1,
|
||||
dcoffset: -0.5,
|
||||
...properties,
|
||||
});
|
||||
shape: getModulationShapeInput(shape),
|
||||
dcoffset,
|
||||
min: dcoffset * depth,
|
||||
max: dcoffset * depth + depth,
|
||||
curve: 1,
|
||||
...props,
|
||||
};
|
||||
|
||||
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
||||
}
|
||||
|
||||
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
|
||||
@@ -370,31 +397,76 @@ function getFilterType(ftype) {
|
||||
return typeof ftype === 'number' ? filterTypes[Math.floor(_mod(ftype, filterTypes.length))] : ftype;
|
||||
}
|
||||
|
||||
let reverbs = {};
|
||||
let hasChanged = (now, before) => now !== undefined && now !== before;
|
||||
function getReverb(orbit, duration, fade, lp, dim, ir, channels) {
|
||||
// If no reverb has been created for a given orbit, create one
|
||||
if (!reverbs[orbit]) {
|
||||
const ac = getAudioContext();
|
||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
||||
connectToDestination(reverb, channels);
|
||||
reverbs[orbit] = reverb;
|
||||
//type orbit {
|
||||
// gain: number,
|
||||
// reverbNode: reverbNode
|
||||
// delayNode: delayNode
|
||||
//}
|
||||
let orbits = {};
|
||||
function connectToOrbit(node, orbit) {
|
||||
if (orbits[orbit] == null) {
|
||||
errorLogger(new Error('target orbit does not exist'), 'superdough');
|
||||
}
|
||||
node.connect(orbits[orbit].gain);
|
||||
}
|
||||
|
||||
function setOrbit(audioContext, orbit, channels) {
|
||||
if (orbits[orbit] == null) {
|
||||
orbits[orbit] = {
|
||||
gain: new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }),
|
||||
};
|
||||
connectToDestination(orbits[orbit].gain, channels);
|
||||
}
|
||||
}
|
||||
function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1) {
|
||||
const targetArr = [targetOrbit].flat();
|
||||
|
||||
targetArr.forEach((target) => {
|
||||
if (orbits[target] == null) {
|
||||
errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough');
|
||||
return;
|
||||
}
|
||||
webAudioTimeout(
|
||||
audioContext,
|
||||
() => {
|
||||
orbits[target].gain.gain.cancelScheduledValues(t);
|
||||
const currVal = orbits[target].gain.gain.value;
|
||||
orbits[target].gain.gain.linearRampToValueAtTime(clamp(1 - Math.pow(duckdepth, 0.5), 0.01, currVal), t);
|
||||
orbits[target].gain.gain.exponentialRampToValueAtTime(1, t + Math.max(0.002, attacktime));
|
||||
},
|
||||
0,
|
||||
t - 0.01,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
let hasChanged = (now, before) => now !== undefined && now !== before;
|
||||
function getReverb(orbit, duration, fade, lp, dim, ir, irspeed, irbegin) {
|
||||
// If no reverb has been created for a given orbit, create one
|
||||
if (!orbits[orbit].reverbNode) {
|
||||
const ac = getAudioContext();
|
||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin);
|
||||
connectToOrbit(reverb, orbit);
|
||||
orbits[orbit].reverbNode = reverb;
|
||||
}
|
||||
|
||||
if (
|
||||
hasChanged(duration, reverbs[orbit].duration) ||
|
||||
hasChanged(fade, reverbs[orbit].fade) ||
|
||||
hasChanged(lp, reverbs[orbit].lp) ||
|
||||
hasChanged(dim, reverbs[orbit].dim) ||
|
||||
reverbs[orbit].ir !== ir
|
||||
hasChanged(duration, orbits[orbit].reverbNode.duration) ||
|
||||
hasChanged(fade, orbits[orbit].reverbNode.fade) ||
|
||||
hasChanged(lp, orbits[orbit].reverbNode.lp) ||
|
||||
hasChanged(dim, orbits[orbit].reverbNode.dim) ||
|
||||
hasChanged(irspeed, orbits[orbit].reverbNode.irspeed) ||
|
||||
hasChanged(irbegin, orbits[orbit].reverbNode.irbegin) ||
|
||||
orbits[orbit].reverbNode.ir !== ir
|
||||
) {
|
||||
// only regenerate when something has changed
|
||||
// avoids endless regeneration on things like
|
||||
// stack(s("a"), s("b").rsize(8)).room(.5)
|
||||
// this only works when args may stay undefined until here
|
||||
// setting default values breaks this
|
||||
reverbs[orbit].generate(duration, fade, lp, dim, ir);
|
||||
orbits[orbit].reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin);
|
||||
}
|
||||
return reverbs[orbit];
|
||||
return orbits[orbit].reverbNode;
|
||||
}
|
||||
|
||||
export let analysers = {},
|
||||
@@ -437,8 +509,7 @@ function effectSend(input, effect, wet) {
|
||||
}
|
||||
|
||||
export function resetGlobalEffects() {
|
||||
delays = {};
|
||||
reverbs = {};
|
||||
orbits = {};
|
||||
analysers = {};
|
||||
analysersData = {};
|
||||
}
|
||||
@@ -450,9 +521,10 @@ function mapChannelNumbers(channels) {
|
||||
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||
}
|
||||
|
||||
export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
|
||||
// new: t is always expected to be the absolute target onset time
|
||||
const ac = getAudioContext();
|
||||
t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
|
||||
|
||||
let { stretch } = value;
|
||||
if (stretch != null) {
|
||||
//account for phase vocoder latency
|
||||
@@ -478,15 +550,25 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
}
|
||||
// destructure
|
||||
let {
|
||||
tremolo,
|
||||
tremolosync,
|
||||
tremolodepth = 1,
|
||||
tremoloskew,
|
||||
tremolophase = 0,
|
||||
tremoloshape,
|
||||
s = getDefaultValue('s'),
|
||||
bank,
|
||||
source,
|
||||
gain = getDefaultValue('gain'),
|
||||
postgain = getDefaultValue('postgain'),
|
||||
density = getDefaultValue('density'),
|
||||
duckorbit,
|
||||
duckattack,
|
||||
duckdepth,
|
||||
// filters
|
||||
fanchor = getDefaultValue('fanchor'),
|
||||
drive = 0.69,
|
||||
release = 0,
|
||||
// low pass
|
||||
cutoff,
|
||||
lpenv,
|
||||
@@ -519,7 +601,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
phasercenter,
|
||||
//
|
||||
coarse,
|
||||
|
||||
crush,
|
||||
dry,
|
||||
shape,
|
||||
shapevol = getDefaultValue('shapevol'),
|
||||
distort,
|
||||
@@ -537,6 +621,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
roomdim,
|
||||
roomsize,
|
||||
ir,
|
||||
irspeed,
|
||||
irbegin,
|
||||
i = getDefaultValue('i'),
|
||||
velocity = getDefaultValue('velocity'),
|
||||
analyze, // analyser wet
|
||||
@@ -553,7 +639,13 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
const orbitChannels = mapChannelNumbers(
|
||||
multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'),
|
||||
);
|
||||
|
||||
const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels;
|
||||
setOrbit(ac, orbit, channels, t, cycle, cps);
|
||||
|
||||
if (duckorbit != null) {
|
||||
duckOrbit(ac, duckorbit, t, duckattack, duckdepth);
|
||||
}
|
||||
|
||||
gain = applyGainCurve(nanFallback(gain, 1));
|
||||
postgain = applyGainCurve(postgain);
|
||||
@@ -561,8 +653,11 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
distortvol = applyGainCurve(distortvol);
|
||||
delay = applyGainCurve(delay);
|
||||
velocity = applyGainCurve(velocity);
|
||||
tremolodepth = applyGainCurve(tremolodepth);
|
||||
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
|
||||
|
||||
const end = t + hapDuration;
|
||||
const endWithRelease = end + release;
|
||||
const chainID = Math.round(Math.random() * 1000000);
|
||||
|
||||
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
|
||||
@@ -637,7 +732,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
lprelease,
|
||||
lpenv,
|
||||
t,
|
||||
t + hapDuration,
|
||||
end,
|
||||
fanchor,
|
||||
ftype,
|
||||
drive,
|
||||
@@ -661,7 +756,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
hprelease,
|
||||
hpenv,
|
||||
t,
|
||||
t + hapDuration,
|
||||
end,
|
||||
fanchor,
|
||||
);
|
||||
chain.push(hp());
|
||||
@@ -672,20 +767,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
|
||||
if (bandf !== undefined) {
|
||||
let bp = () =>
|
||||
createFilter(
|
||||
ac,
|
||||
'bandpass',
|
||||
bandf,
|
||||
bandq,
|
||||
bpattack,
|
||||
bpdecay,
|
||||
bpsustain,
|
||||
bprelease,
|
||||
bpenv,
|
||||
t,
|
||||
t + hapDuration,
|
||||
fanchor,
|
||||
);
|
||||
createFilter(ac, 'bandpass', bandf, bandq, bpattack, bpdecay, bpsustain, bprelease, bpenv, t, end, fanchor);
|
||||
chain.push(bp());
|
||||
if (ftype === '24db') {
|
||||
chain.push(bp());
|
||||
@@ -703,6 +785,33 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
|
||||
distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol }));
|
||||
|
||||
if (tremolosync != null) {
|
||||
tremolo = cps * tremolosync;
|
||||
}
|
||||
|
||||
if (tremolo !== undefined) {
|
||||
// Allow clipping of modulator for more dynamic possiblities, and to prevent speaker overload
|
||||
// EX: a triangle waveform will clip like this /-\ when the depth is above 1
|
||||
const gain = Math.max(1 - tremolodepth, 0);
|
||||
const amGain = new GainNode(ac, { gain });
|
||||
|
||||
const time = cycle / cps;
|
||||
const lfo = getLfo(ac, t, endWithRelease, {
|
||||
skew: tremoloskew ?? (tremoloshape != null ? 0.5 : 1),
|
||||
frequency: tremolo,
|
||||
depth: tremolodepth,
|
||||
time,
|
||||
dcoffset: 0,
|
||||
shape: tremoloshape,
|
||||
phaseoffset: tremolophase,
|
||||
min: 0,
|
||||
max: 1,
|
||||
curve: 1.5,
|
||||
});
|
||||
lfo.connect(amGain.gain);
|
||||
chain.push(amGain);
|
||||
}
|
||||
|
||||
compressorThreshold !== undefined &&
|
||||
chain.push(
|
||||
getCompressor(ac, compressorThreshold, compressorRatio, compressorKnee, compressorAttack, compressorRelease),
|
||||
@@ -716,19 +825,18 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
}
|
||||
// phaser
|
||||
if (phaser !== undefined && phaserdepth > 0) {
|
||||
const phaserFX = getPhaser(t, t + hapDuration, phaser, phaserdepth, phasercenter, phasersweep);
|
||||
const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep);
|
||||
chain.push(phaserFX);
|
||||
}
|
||||
|
||||
// last gain
|
||||
const post = new GainNode(ac, { gain: postgain });
|
||||
chain.push(post);
|
||||
connectToDestination(post, channels);
|
||||
|
||||
// delay
|
||||
let delaySend;
|
||||
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||
const delayNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
|
||||
const delayNode = getDelay(orbit, delaytime, delayfeedback, t);
|
||||
delaySend = effectSend(post, delayNode, delay);
|
||||
audioNodes.push(delaySend);
|
||||
}
|
||||
@@ -746,7 +854,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
}
|
||||
roomIR = await loadBuffer(url, ac, ir, 0);
|
||||
}
|
||||
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels);
|
||||
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
|
||||
reverbSend = effectSend(post, reverbNode, room);
|
||||
audioNodes.push(reverbSend);
|
||||
}
|
||||
@@ -758,6 +866,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5) => {
|
||||
analyserSend = effectSend(post, analyserNode, 1);
|
||||
audioNodes.push(analyserSend);
|
||||
}
|
||||
if (dry != null) {
|
||||
dry = applyGainCurve(dry);
|
||||
const dryGain = new GainNode(ac, { gain: dry });
|
||||
chain.push(dryGain);
|
||||
connectToOrbit(dryGain, orbit);
|
||||
} else {
|
||||
connectToOrbit(post, orbit);
|
||||
}
|
||||
|
||||
// connect chain elements together
|
||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||
|
||||
@@ -9,12 +9,13 @@ import {
|
||||
getVibratoOscillator,
|
||||
webAudioTimeout,
|
||||
getWorklet,
|
||||
noises,
|
||||
} from './helpers.mjs';
|
||||
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
||||
|
||||
const getFrequencyFromValue = (value) => {
|
||||
const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||
let { note, freq } = value;
|
||||
note = note || 36;
|
||||
note = note || defaultNote;
|
||||
if (typeof note === 'string') {
|
||||
note = noteToMidi(note); // e.g. c3 => 48
|
||||
}
|
||||
@@ -40,7 +41,17 @@ const waveformAliases = [
|
||||
['saw', 'sawtooth'],
|
||||
['sin', 'sine'],
|
||||
];
|
||||
const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||
|
||||
function makeSaturationCurve(amount, n_samples) {
|
||||
const k = typeof amount === 'number' ? amount : 50;
|
||||
const curve = new Float32Array(n_samples);
|
||||
|
||||
for (let i = 0; i < n_samples; i++) {
|
||||
const x = (i * 2) / n_samples - 1;
|
||||
curve[i] = Math.tanh(x * k);
|
||||
}
|
||||
return curve;
|
||||
}
|
||||
|
||||
export function registerSynthSounds() {
|
||||
[...waveforms].forEach((s) => {
|
||||
@@ -84,6 +95,75 @@ export function registerSynthSounds() {
|
||||
{ type: 'synth', prebake: true },
|
||||
);
|
||||
});
|
||||
|
||||
registerSound(
|
||||
'sbd',
|
||||
(t, value, onended) => {
|
||||
const { duration, decay = 0.5, pdecay = 0.5, penv = 36, clip } = value;
|
||||
const ctx = getAudioContext();
|
||||
const attackhold = 0.02;
|
||||
const noiselvl = 1.2;
|
||||
const noisedecay = 0.025;
|
||||
const mixGain = 1;
|
||||
|
||||
const o = ctx.createOscillator();
|
||||
o.type = 'triangle';
|
||||
o.frequency.value = getFrequencyFromValue(value, 29);
|
||||
o.detune.setValueAtTime(penv * 100, 0);
|
||||
o.detune.setValueAtTime(penv * 100, t);
|
||||
o.detune.exponentialRampToValueAtTime(0.001, t + pdecay);
|
||||
const g = gainNode(1);
|
||||
g.gain.setValueAtTime(1, t + attackhold);
|
||||
g.gain.exponentialRampToValueAtTime(0.001, t + attackhold + decay);
|
||||
o.start(t);
|
||||
|
||||
const noise = getNoiseOscillator('brown', t, 2);
|
||||
const noiseGain = gainNode(1);
|
||||
noiseGain.gain.setValueAtTime(noiselvl, t);
|
||||
noiseGain.gain.exponentialRampToValueAtTime(0.001, t + noisedecay);
|
||||
|
||||
const sat = new WaveShaperNode(ctx);
|
||||
// tri to sine diode shaper emulation
|
||||
sat.curve = makeSaturationCurve(2, ctx.sampleRate);
|
||||
|
||||
const mix = gainNode(mixGain);
|
||||
|
||||
o.onended = () => {
|
||||
o.disconnect();
|
||||
g.disconnect();
|
||||
sat.disconnect();
|
||||
noise.node.disconnect();
|
||||
noiseGain.disconnect();
|
||||
mix.disconnect();
|
||||
onended();
|
||||
};
|
||||
|
||||
const node = o.connect(sat).connect(g).connect(mix);
|
||||
noise.node.connect(noiseGain).connect(mix);
|
||||
|
||||
const holdEnd = t + decay;
|
||||
let end = holdEnd + 0.01;
|
||||
if (clip != null) {
|
||||
end = Math.min(t + clip * duration, end);
|
||||
}
|
||||
|
||||
// prevent clicking
|
||||
mix.gain.setValueAtTime(mixGain, end - 0.01);
|
||||
mix.gain.linearRampToValueAtTime(0, end);
|
||||
|
||||
o.stop(end);
|
||||
noise.stop(end);
|
||||
|
||||
return {
|
||||
node,
|
||||
stop: (endTime) => {
|
||||
o.stop(endTime);
|
||||
},
|
||||
};
|
||||
},
|
||||
{ type: 'synth', prebake: true },
|
||||
);
|
||||
|
||||
registerSound(
|
||||
'supersaw',
|
||||
(begin, value, onended) => {
|
||||
|
||||
@@ -72,3 +72,7 @@ export const getSoundIndex = (n, numSounds) => {
|
||||
export function cycleToSeconds(cycle, cps) {
|
||||
return cycle / cps;
|
||||
}
|
||||
|
||||
export function secondsToCycle(t, cps) {
|
||||
return t * cps;
|
||||
}
|
||||
|
||||
@@ -8,18 +8,28 @@ import FFT from './fft.js';
|
||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
const _mod = (n, m) => ((n % m) + m) % m;
|
||||
|
||||
// Restrict phase to the range [0, maxPhase) via wrapping
|
||||
function wrapPhase(phase, maxPhase = 1) {
|
||||
if (phase >= maxPhase) {
|
||||
phase -= maxPhase;
|
||||
} else if (phase < 0) {
|
||||
phase += maxPhase;
|
||||
}
|
||||
return phase;
|
||||
}
|
||||
const blockSize = 128;
|
||||
// adjust waveshape to remove frequencies above nyquist to prevent aliasing
|
||||
// Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
|
||||
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
||||
function polyBlep(phase, dt) {
|
||||
// 0 <= phase < 1
|
||||
dt = Math.min(dt, 1 - dt);
|
||||
// Start of cycle
|
||||
if (phase < dt) {
|
||||
phase /= dt;
|
||||
// 2 * (phase - phase^2/2 - 0.5)
|
||||
return phase + phase - phase * phase - 1;
|
||||
}
|
||||
|
||||
// -1 < phase < 0
|
||||
// End of cycle
|
||||
else if (phase > 1 - dt) {
|
||||
phase = (phase - 1) / dt;
|
||||
// 2 * (phase^2/2 + phase + 0.5)
|
||||
@@ -31,7 +41,7 @@ function polyBlep(phase, dt) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// The order is important for dough integration
|
||||
const waveshapes = {
|
||||
tri(phase, skew = 0.5) {
|
||||
const x = 1 - skew;
|
||||
@@ -81,10 +91,12 @@ function getParamValue(block, param) {
|
||||
}
|
||||
return param[0];
|
||||
}
|
||||
|
||||
const waveShapeNames = Object.keys(waveshapes);
|
||||
class LFOProcessor extends AudioWorkletProcessor {
|
||||
static get parameterDescriptors() {
|
||||
return [
|
||||
{ name: 'begin', defaultValue: 0 },
|
||||
{ name: 'time', defaultValue: 0 },
|
||||
{ name: 'end', defaultValue: 0 },
|
||||
{ name: 'frequency', defaultValue: 0.5 },
|
||||
@@ -92,7 +104,10 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||
{ name: 'depth', defaultValue: 1 },
|
||||
{ name: 'phaseoffset', defaultValue: 0 },
|
||||
{ name: 'shape', defaultValue: 0 },
|
||||
{ name: 'curve', defaultValue: 1 },
|
||||
{ name: 'dcoffset', defaultValue: 0 },
|
||||
{ name: 'min', defaultValue: 0 },
|
||||
{ name: 'max', defaultValue: 1 },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -109,10 +124,13 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
|
||||
process(inputs, outputs, parameters) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const begin = parameters['begin'][0];
|
||||
if (currentTime >= parameters.end[0]) {
|
||||
return false;
|
||||
}
|
||||
if (currentTime <= begin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const output = outputs[0];
|
||||
const frequency = parameters['frequency'][0];
|
||||
@@ -122,7 +140,11 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||
const skew = parameters['skew'][0];
|
||||
const phaseoffset = parameters['phaseoffset'][0];
|
||||
|
||||
const curve = parameters['curve'][0];
|
||||
|
||||
const dcoffset = parameters['dcoffset'][0];
|
||||
const min = parameters['min'][0];
|
||||
const max = parameters['max'][0];
|
||||
const shape = waveShapeNames[parameters['shape'][0]];
|
||||
|
||||
const blockSize = output[0].length ?? 0;
|
||||
@@ -130,12 +152,12 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||
if (this.phase == null) {
|
||||
this.phase = _mod(time * frequency + phaseoffset, 1);
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
const dt = frequency / sampleRate;
|
||||
for (let n = 0; n < blockSize; n++) {
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
const modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
|
||||
output[i][n] = modval;
|
||||
let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
|
||||
modval = Math.pow(modval, curve);
|
||||
output[i][n] = clamp(modval, min, max);
|
||||
}
|
||||
this.incrementPhase(dt);
|
||||
}
|
||||
@@ -291,7 +313,6 @@ class LadderProcessor extends AudioWorkletProcessor {
|
||||
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
|
||||
|
||||
let cutoff = parameters.frequency[0];
|
||||
// eslint-disable-next-line no-undef
|
||||
cutoff = (cutoff * 2 * _PI) / sampleRate;
|
||||
cutoff = cutoff > 1 ? 1 : cutoff;
|
||||
|
||||
@@ -424,18 +445,13 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
];
|
||||
}
|
||||
process(input, outputs, params) {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (currentTime <= params.begin[0]) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
if (currentTime >= params.end[0]) {
|
||||
// this.port.postMessage({ type: 'onended' });
|
||||
return false;
|
||||
}
|
||||
let frequency = params.frequency[0];
|
||||
//apply detune in cents
|
||||
frequency = frequency * Math.pow(2, params.detune[0] / 1200);
|
||||
|
||||
const output = outputs[0];
|
||||
const voices = params.voices[0];
|
||||
@@ -446,9 +462,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
|
||||
for (let n = 0; n < voices; n++) {
|
||||
const isOdd = (n & 1) == 1;
|
||||
|
||||
//applies unison "spread" detune in semitones
|
||||
const freq = applySemitoneDetuneToFrequency(frequency, getUnisonDetune(voices, freqspread, n));
|
||||
let gainL = gain1;
|
||||
let gainR = gain2;
|
||||
// invert right and left gain
|
||||
@@ -456,21 +469,21 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
gainL = gain2;
|
||||
gainR = gain1;
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
const dt = freq / sampleRate;
|
||||
|
||||
for (let i = 0; i < output[0].length; i++) {
|
||||
// Main detuning
|
||||
let freq = applySemitoneDetuneToFrequency(params.frequency[i] ?? params.frequency[0], params.detune[0] / 100);
|
||||
// Individual voice detuning
|
||||
freq = applySemitoneDetuneToFrequency(freq, getUnisonDetune(voices, freqspread, n));
|
||||
// We must wrap this here because it is passed into sawblep below which
|
||||
// has domain [0, 1]
|
||||
const dt = _mod(freq / sampleRate, 1);
|
||||
this.phase[n] = this.phase[n] ?? Math.random();
|
||||
const v = waveshapes.sawblep(this.phase[n], dt);
|
||||
|
||||
output[0][i] = output[0][i] + v * gainL;
|
||||
output[1][i] = output[1][i] + v * gainR;
|
||||
|
||||
this.phase[n] += dt;
|
||||
|
||||
if (this.phase[n] > 1.0) {
|
||||
this.phase[n] = this.phase[n] - 1;
|
||||
}
|
||||
this.phase[n] = wrapPhase(this.phase[n] + dt);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -479,7 +492,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
|
||||
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
||||
|
||||
// Phase Vocoder sourced from // sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
||||
// Phase Vocoder sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
||||
const BUFFERED_BLOCK_SIZE = 2048;
|
||||
|
||||
function genHannWindow(length) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/tonal",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Tonal functions for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
+22
-17
@@ -88,13 +88,14 @@ function scaleOffset(scale, offset, note) {
|
||||
* @returns Pattern
|
||||
* @memberof Pattern
|
||||
* @name transpose
|
||||
* @synonyms trans
|
||||
* @example
|
||||
* "c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).note()
|
||||
* @example
|
||||
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
|
||||
*/
|
||||
|
||||
export const transpose = register('transpose', function (intervalOrSemitones, pat) {
|
||||
export const { transpose, trans } = register(['transpose', 'trans'], function transposeFn(intervalOrSemitones, pat) {
|
||||
return pat.withHap((hap) => {
|
||||
const note = hap.value.note ?? hap.value;
|
||||
if (typeof note === 'number') {
|
||||
@@ -142,6 +143,7 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
|
||||
* @name scaleTranspose
|
||||
* @param {offset} offset number of steps inside the scale
|
||||
* @returns Pattern
|
||||
* @synonyms scaleTrans, strans
|
||||
* @example
|
||||
* "-8 [2,4,6]"
|
||||
* .scale('C4 bebop major')
|
||||
@@ -149,22 +151,25 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
|
||||
* .note()
|
||||
*/
|
||||
|
||||
export const scaleTranspose = register('scaleTranspose', function (offset /* : number | string */, pat) {
|
||||
return pat.withHap((hap) => {
|
||||
if (!hap.context.scale) {
|
||||
throw new Error('can only use scaleTranspose after .scale');
|
||||
}
|
||||
if (typeof hap.value === 'object')
|
||||
return hap.withValue(() => ({
|
||||
...hap.value,
|
||||
note: scaleOffset(hap.context.scale, Number(offset), hap.value.note),
|
||||
}));
|
||||
if (typeof hap.value !== 'string') {
|
||||
throw new Error('can only use scaleTranspose with notes');
|
||||
}
|
||||
return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value));
|
||||
});
|
||||
});
|
||||
export const { scaleTranspose, scaleTrans, strans } = register(
|
||||
['scaleTranspose', 'scaleTrans', 'strans'],
|
||||
function (offset /* : number | string */, pat) {
|
||||
return pat.withHap((hap) => {
|
||||
if (!hap.context.scale) {
|
||||
throw new Error('can only use scaleTranspose after .scale');
|
||||
}
|
||||
if (typeof hap.value === 'object')
|
||||
return hap.withValue(() => ({
|
||||
...hap.value,
|
||||
note: scaleOffset(hap.context.scale, Number(offset), hap.value.note),
|
||||
}));
|
||||
if (typeof hap.value !== 'string') {
|
||||
throw new Error('can only use scaleTranspose with notes');
|
||||
}
|
||||
return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/transpiler",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/web",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
|
||||
"module": "web.mjs",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/webaudio",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"description": "Web Audio helpers for Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -15,14 +15,10 @@ const hap2value = (hap) => {
|
||||
return hap.value;
|
||||
};
|
||||
|
||||
export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps);
|
||||
// uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004
|
||||
export const webaudioOutput = (hap, deadline, hapDuration, cps, t) => {
|
||||
return superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration, cps);
|
||||
};
|
||||
|
||||
Pattern.prototype.webaudio = function () {
|
||||
return this.onTrigger(webaudioOutputTrigger);
|
||||
// TODO: refactor output callbacks to eliminate deadline
|
||||
export const webaudioOutput = (hap, _deadline, hapDuration, cps, t) => {
|
||||
return superdough(hap2value(hap), t, hapDuration, cps, hap.whole?.begin.valueOf());
|
||||
};
|
||||
|
||||
export function webaudioRepl(options = {}) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/xen",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"description": "Xenharmonic API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -3036,6 +3036,145 @@ exports[`runs examples > example "dry" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "duckattack" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 0/1 → 1/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/8 → 1/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 1/4 → 3/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 3/8 → 1/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 1/2 → 5/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 5/8 → 3/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 3/4 → 7/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 7/8 → 1/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
|
||||
"[ 1/1 → 9/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 9/8 → 5/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
|
||||
"[ 5/4 → 11/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 11/8 → 3/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
|
||||
"[ 3/2 → 13/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 13/8 → 7/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
|
||||
"[ 7/4 → 15/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
|
||||
"[ 15/8 → 2/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
|
||||
"[ 2/1 → 17/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 17/8 → 9/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
|
||||
"[ 9/4 → 19/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 19/8 → 5/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
|
||||
"[ 5/2 → 21/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 21/8 → 11/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
|
||||
"[ 11/4 → 23/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
|
||||
"[ 23/8 → 3/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 3/1 → 25/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 25/8 → 13/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 13/4 → 27/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 27/8 → 7/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 7/2 → 29/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 29/8 → 15/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 15/4 → 31/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 31/8 → 4/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "duckdepth" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 0/1 → 1/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/8 → 1/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 1/4 → 3/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 3/8 → 1/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 1/2 → 5/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 5/8 → 3/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 3/4 → 7/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 7/8 → 1/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.9 ]",
|
||||
"[ 1/1 → 9/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 9/8 → 5/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.9 ]",
|
||||
"[ 5/4 → 11/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 11/8 → 3/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.9 ]",
|
||||
"[ 3/2 → 13/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 13/8 → 7/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.9 ]",
|
||||
"[ 7/4 → 15/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.9 ]",
|
||||
"[ 15/8 → 2/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.6 ]",
|
||||
"[ 2/1 → 17/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 17/8 → 9/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.6 ]",
|
||||
"[ 9/4 → 19/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 19/8 → 5/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.6 ]",
|
||||
"[ 5/2 → 21/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 21/8 → 11/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.6 ]",
|
||||
"[ 11/4 → 23/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.6 ]",
|
||||
"[ 23/8 → 3/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0 ]",
|
||||
"[ 3/1 → 25/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 25/8 → 13/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0 ]",
|
||||
"[ 13/4 → 27/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 27/8 → 7/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0 ]",
|
||||
"[ 7/2 → 29/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 29/8 → 15/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0 ]",
|
||||
"[ 15/4 → 31/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
"[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0 ]",
|
||||
"[ 31/8 → 4/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "duckorbit" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
"[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "duration" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]",
|
||||
@@ -3271,6 +3410,39 @@ exports[`runs examples > example "euclidRot" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "euclidish" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/12 | s:hh pan:0.5 ]",
|
||||
"[ 13/84 → 5/21 | s:hh pan:0.5606253170575308 ]",
|
||||
"[ 15/56 → 59/168 | s:hh pan:0.604413082836085 ]",
|
||||
"[ 71/168 → 85/168 | s:hh pan:0.6629314122869361 ]",
|
||||
"[ 97/168 → 37/56 | s:hh pan:0.7190455010067492 ]",
|
||||
"[ 29/42 → 65/84 | s:hh pan:0.7580531369037533 ]",
|
||||
"[ 71/84 → 13/14 | s:hh pan:0.8080762739548087 ]",
|
||||
"[ 1/1 → 13/12 | s:hh pan:0.8535533905932737 ]",
|
||||
"[ 451099417/393511398 → 322594689/262340932 | s:hh pan:0.891768001805729 ]",
|
||||
"[ 335923379/262340932 → 536677685/393511398 | s:hh pan:0.9222657853371297 ]",
|
||||
"[ 1122946175/787022796 → 99044284/65585233 | s:hh pan:0.9501869591788796 ]",
|
||||
"[ 1238122213/787022796 → 651853723/393511398 | s:hh pan:0.9721673436944069 ]",
|
||||
"[ 335923379/196755699 → 469759583/262340932 | s:hh pan:0.9868472639237561 ]",
|
||||
"[ 729434777/393511398 → 1524454787/787022796 | s:hh pan:0.9967009321321423 ]",
|
||||
"[ 2/1 → 25/12 | s:hh pan:1 ]",
|
||||
"[ 15/7 → 187/84 | s:hh pan:0.9968561049466214 ]",
|
||||
"[ 16/7 → 199/84 | s:hh pan:0.9874639560909118 ]",
|
||||
"[ 17/7 → 211/84 | s:hh pan:0.9719416651541839 ]",
|
||||
"[ 18/7 → 223/84 | s:hh pan:0.9504844339512095 ]",
|
||||
"[ 19/7 → 235/84 | s:hh pan:0.9233620996141421 ]",
|
||||
"[ 20/7 → 247/84 | s:hh pan:0.890915741234015 ]",
|
||||
"[ 3/1 → 37/12 | s:hh pan:0.8535533905932737 ]",
|
||||
"[ 1238122213/393511398 → 847276553/262340932 | s:hh pan:0.8106731928589048 ]",
|
||||
"[ 860605243/262340932 → 1323700481/393511398 | s:hh pan:0.7677528833339 ]",
|
||||
"[ 2696991767/787022796 → 230214750/65585233 | s:hh pan:0.7175585019834292 ]",
|
||||
"[ 2812167805/787022796 → 1438876519/393511398 | s:hh pan:0.6644931595798675 ]",
|
||||
"[ 729434777/196755699 → 994441447/262340932 | s:hh pan:0.6139286689554151 ]",
|
||||
"[ 1516457573/393511398 → 3098500379/787022796 | s:hh pan:0.557342689325327 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "every" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:g3 ]",
|
||||
@@ -3706,6 +3878,144 @@ exports[`runs examples > example "fmsustain" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmwave" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 1/16 → 1/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 1/8 → 3/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 3/16 → 1/4 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 1/4 → 5/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 5/16 → 3/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 3/8 → 7/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 7/16 → 1/2 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 1/2 → 9/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 9/16 → 5/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 5/8 → 11/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 11/16 → 3/4 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 3/4 → 13/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 13/16 → 7/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 7/8 → 15/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 15/16 → 1/1 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||
"[ 1/1 → 17/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 17/16 → 9/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 9/8 → 19/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 19/16 → 5/4 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 5/4 → 21/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 21/16 → 11/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 11/8 → 23/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 23/16 → 3/2 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 3/2 → 25/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 25/16 → 13/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 13/8 → 27/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 27/16 → 7/4 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 7/4 → 29/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 29/16 → 15/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 15/8 → 31/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 31/16 → 2/1 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||
"[ 2/1 → 33/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 33/16 → 17/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 17/8 → 35/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 35/16 → 9/4 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 9/4 → 37/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 37/16 → 19/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 19/8 → 39/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 39/16 → 5/2 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 5/2 → 41/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 41/16 → 21/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 21/8 → 43/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 43/16 → 11/4 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 11/4 → 45/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 45/16 → 23/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 23/8 → 47/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 47/16 → 3/1 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||
"[ 3/1 → 49/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 49/16 → 25/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 25/8 → 51/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 51/16 → 13/4 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 13/4 → 53/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 53/16 → 27/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 27/8 → 55/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 55/16 → 7/2 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 7/2 → 57/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 57/16 → 29/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 29/8 → 59/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 59/16 → 15/4 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 15/4 → 61/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 61/16 → 31/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 31/8 → 63/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
"[ 63/16 → 4/1 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmwave" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 1/16 → 1/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 1/8 → 3/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 3/16 → 1/4 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 1/4 → 5/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 5/16 → 3/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 3/8 → 7/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 7/16 → 1/2 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 1/2 → 9/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 9/16 → 5/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 5/8 → 11/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 11/16 → 3/4 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 3/4 → 13/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 13/16 → 7/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 7/8 → 15/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 15/16 → 1/1 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 1/1 → 17/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 17/16 → 9/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 9/8 → 19/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 19/16 → 5/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 5/4 → 21/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 21/16 → 11/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 11/8 → 23/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 23/16 → 3/2 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 3/2 → 25/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 25/16 → 13/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 13/8 → 27/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 27/16 → 7/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 7/4 → 29/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 29/16 → 15/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 15/8 → 31/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 31/16 → 2/1 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 2/1 → 33/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 33/16 → 17/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 17/8 → 35/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 35/16 → 9/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 9/4 → 37/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 37/16 → 19/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 19/8 → 39/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 39/16 → 5/2 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 5/2 → 41/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 41/16 → 21/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 21/8 → 43/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 43/16 → 11/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 11/4 → 45/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 45/16 → 23/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 23/8 → 47/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 47/16 → 3/1 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 3/1 → 49/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 49/16 → 25/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 25/8 → 51/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 51/16 → 13/4 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 13/4 → 53/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 53/16 → 27/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 27/8 → 55/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 55/16 → 7/2 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 7/2 → 57/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 57/16 → 29/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 29/8 → 59/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 59/16 → 15/4 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 15/4 → 61/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 61/16 → 31/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 31/8 → 63/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
"[ 63/16 → 4/1 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "focus" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:sd ]",
|
||||
@@ -4565,6 +4875,43 @@ exports[`runs examples > example "irand" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "irbegin" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 1/8 → 1/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 1/4 → 3/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 3/8 → 1/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 1/2 → 5/8 | s:brk speed:0.5 unit:c begin:0.25 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 5/8 → 3/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 3/4 → 7/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 7/8 → 1/1 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 1/1 → 9/8 | s:brk speed:0.5 unit:c begin:0.5 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 9/8 → 5/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 5/4 → 11/8 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 11/8 → 3/2 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 3/2 → 13/8 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 13/8 → 7/4 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 7/4 → 15/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 15/8 → 2/1 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0 roomsize:0.6 ]",
|
||||
"[ 2/1 → 17/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 17/8 → 9/4 | s:brk speed:0.5 unit:c begin:0.875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 9/4 → 19/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 19/8 → 5/2 | s:brk speed:0.5 unit:c begin:0.8125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 5/2 → 21/8 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 21/8 → 11/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 11/4 → 23/8 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 23/8 → 3/1 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 3/1 → 25/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 25/8 → 13/4 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 13/4 → 27/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 27/8 → 7/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 7/2 → 29/8 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 29/8 → 15/4 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 15/4 → 31/8 | s:brk speed:0.5 unit:c begin:0.75 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
"[ 31/8 → 4/1 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.65 irspeed:-2 irbegin:0.5 roomsize:0.6 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "iresponse" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:bd room:0.8 ir:shaker_large i:0 ]",
|
||||
@@ -4586,6 +4933,43 @@ exports[`runs examples > example "iresponse" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "irspeed" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 1/8 → 1/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 1/4 → 3/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 3/8 → 1/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 1/2 → 5/8 | s:brk speed:0.5 unit:c begin:0.25 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 5/8 → 3/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 3/4 → 7/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 7/8 → 1/1 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 1/1 → 9/8 | s:brk speed:0.5 unit:c begin:0.5 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 9/8 → 5/4 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 5/4 → 11/8 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 11/8 → 3/2 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 3/2 → 13/8 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 13/8 → 7/4 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 7/4 → 15/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 15/8 → 2/1 | s:brk speed:0.5 unit:c begin:0.5625 clip:1 ir:swpad i:4 room:0.2 irspeed:2 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 2/1 → 17/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 17/8 → 9/4 | s:brk speed:0.5 unit:c begin:0.875 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 9/4 → 19/8 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 19/8 → 5/2 | s:brk speed:0.5 unit:c begin:0.8125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 5/2 → 21/8 | s:brk speed:0.5 unit:c begin:0.4375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 21/8 → 11/4 | s:brk speed:0.5 unit:c begin:0.125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 11/4 → 23/8 | s:brk speed:0.5 unit:c begin:0.625 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 23/8 → 3/1 | s:brk speed:0.5 unit:c begin:0 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 3/1 → 25/8 | s:brk speed:0.5 unit:c begin:0.1875 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 25/8 → 13/4 | s:brk speed:0.5 unit:c begin:0.3125 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 13/4 → 27/8 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 27/8 → 7/2 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 7/2 → 29/8 | s:brk speed:0.5 unit:c begin:0.375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 29/8 → 15/4 | s:brk speed:0.5 unit:c begin:0.9375 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 15/4 → 31/8 | s:brk speed:0.5 unit:c begin:0.75 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
"[ 31/8 → 4/1 | s:brk speed:0.5 unit:c begin:0.6875 clip:1 ir:swpad i:4 room:0.2 irspeed:1 irbegin:0.5 roomsize:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "isaw" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:c3 clip:1 ]",
|
||||
@@ -5147,6 +5531,40 @@ exports[`runs examples > example "lock" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "log" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd ]",
|
||||
"[ 1/2 → 1/1 | s:sd ]",
|
||||
"[ 1/1 → 3/2 | s:bd ]",
|
||||
"[ 3/2 → 2/1 | s:sd ]",
|
||||
"[ 2/1 → 5/2 | s:bd ]",
|
||||
"[ 5/2 → 3/1 | s:sd ]",
|
||||
"[ 3/1 → 7/2 | s:bd ]",
|
||||
"[ 7/2 → 4/1 | s:sd ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "logValues" example index 0 1`] = `
|
||||
[
|
||||
"[ (0/1 → 1/3) ⇝ 1/2 | s:bd gain:0.25 n:2 ]",
|
||||
"[ 0/1 ⇜ (1/3 → 1/2) | s:bd gain:0.5 n:1 ]",
|
||||
"[ (1/2 → 2/3) ⇝ 1/1 | s:sd gain:0.5 n:1 ]",
|
||||
"[ 1/2 ⇜ (2/3 → 1/1) | s:sd gain:1 n:0 ]",
|
||||
"[ (1/1 → 4/3) ⇝ 3/2 | s:bd gain:0.25 n:2 ]",
|
||||
"[ 1/1 ⇜ (4/3 → 3/2) | s:bd gain:0.5 n:1 ]",
|
||||
"[ (3/2 → 5/3) ⇝ 2/1 | s:sd gain:0.5 n:1 ]",
|
||||
"[ 3/2 ⇜ (5/3 → 2/1) | s:sd gain:1 n:0 ]",
|
||||
"[ (2/1 → 7/3) ⇝ 5/2 | s:bd gain:0.25 n:2 ]",
|
||||
"[ 2/1 ⇜ (7/3 → 5/2) | s:bd gain:0.5 n:1 ]",
|
||||
"[ (5/2 → 8/3) ⇝ 3/1 | s:sd gain:0.5 n:1 ]",
|
||||
"[ 5/2 ⇜ (8/3 → 3/1) | s:sd gain:1 n:0 ]",
|
||||
"[ (3/1 → 10/3) ⇝ 7/2 | s:bd gain:0.25 n:2 ]",
|
||||
"[ 3/1 ⇜ (10/3 → 7/2) | s:bd gain:0.5 n:1 ]",
|
||||
"[ (7/2 → 11/3) ⇝ 4/1 | s:sd gain:0.5 n:1 ]",
|
||||
"[ 7/2 ⇜ (11/3 → 4/1) | s:sd gain:1 n:0 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "loop" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | s:casio loop:1 ]",
|
||||
@@ -5826,6 +6244,48 @@ exports[`runs examples > example "miditouch" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "morph" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh ]",
|
||||
"[ 25/112 → 39/112 | s:hh ]",
|
||||
"[ 27/56 → 17/28 | s:hh ]",
|
||||
"[ 83/112 → 97/112 | s:hh ]",
|
||||
"[ 1/1 → 9/8 | s:hh ]",
|
||||
"[ 137/112 → 151/112 | s:hh ]",
|
||||
"[ 83/56 → 45/28 | s:hh ]",
|
||||
"[ 195/112 → 209/112 | s:hh ]",
|
||||
"[ 2/1 → 17/8 | s:hh ]",
|
||||
"[ 249/112 → 263/112 | s:hh ]",
|
||||
"[ 139/56 → 73/28 | s:hh ]",
|
||||
"[ 307/112 → 321/112 | s:hh ]",
|
||||
"[ 3/1 → 25/8 | s:hh ]",
|
||||
"[ 361/112 → 375/112 | s:hh ]",
|
||||
"[ 195/56 → 101/28 | s:hh ]",
|
||||
"[ 419/112 → 433/112 | s:hh ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "morph" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh ]",
|
||||
"[ 11/56 → 9/28 | s:hh ]",
|
||||
"[ 13/28 → 33/56 | s:hh ]",
|
||||
"[ 41/56 → 6/7 | s:hh ]",
|
||||
"[ 1/1 → 9/8 | s:hh ]",
|
||||
"[ 303934523/262340932 → 673454279/524681864 | s:hh ]",
|
||||
"[ 188758485/131170466 → 820619173/524681864 | s:hh ]",
|
||||
"[ 451099417/262340932 → 967784067/524681864 | s:hh ]",
|
||||
"[ 2/1 → 17/8 | s:hh ]",
|
||||
"[ 15/7 → 127/56 | s:hh ]",
|
||||
"[ 17/7 → 143/56 | s:hh ]",
|
||||
"[ 19/7 → 159/56 | s:hh ]",
|
||||
"[ 3/1 → 25/8 | s:hh ]",
|
||||
"[ 828616387/262340932 → 1722818007/524681864 | s:hh ]",
|
||||
"[ 451099417/131170466 → 1869982901/524681864 | s:hh ]",
|
||||
"[ 975781281/262340932 → 2017147795/524681864 | s:hh ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "mousex" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:C3 ]",
|
||||
@@ -8813,46 +9273,46 @@ exports[`runs examples > example "shrink" example index 3 1`] = `
|
||||
|
||||
exports[`runs examples > example "shuffle" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c s:piano ]",
|
||||
"[ 0/1 → 1/4 | note:e s:piano ]",
|
||||
"[ 1/4 → 1/2 | note:d s:piano ]",
|
||||
"[ 1/2 → 3/4 | note:e s:piano ]",
|
||||
"[ 3/4 → 1/1 | note:f s:piano ]",
|
||||
"[ 1/1 → 5/4 | note:c s:piano ]",
|
||||
"[ 5/4 → 3/2 | note:d s:piano ]",
|
||||
"[ 3/2 → 7/4 | note:e s:piano ]",
|
||||
"[ 7/4 → 2/1 | note:f s:piano ]",
|
||||
"[ 2/1 → 9/4 | note:c s:piano ]",
|
||||
"[ 9/4 → 5/2 | note:d s:piano ]",
|
||||
"[ 1/2 → 3/4 | note:f s:piano ]",
|
||||
"[ 3/4 → 1/1 | note:c s:piano ]",
|
||||
"[ 1/1 → 5/4 | note:e s:piano ]",
|
||||
"[ 5/4 → 3/2 | note:c s:piano ]",
|
||||
"[ 3/2 → 7/4 | note:f s:piano ]",
|
||||
"[ 7/4 → 2/1 | note:d s:piano ]",
|
||||
"[ 2/1 → 9/4 | note:d s:piano ]",
|
||||
"[ 9/4 → 5/2 | note:c s:piano ]",
|
||||
"[ 5/2 → 11/4 | note:e s:piano ]",
|
||||
"[ 11/4 → 3/1 | note:f s:piano ]",
|
||||
"[ 3/1 → 13/4 | note:c s:piano ]",
|
||||
"[ 13/4 → 7/2 | note:d s:piano ]",
|
||||
"[ 7/2 → 15/4 | note:e s:piano ]",
|
||||
"[ 15/4 → 4/1 | note:f s:piano ]",
|
||||
"[ 13/4 → 7/2 | note:e s:piano ]",
|
||||
"[ 7/2 → 15/4 | note:f s:piano ]",
|
||||
"[ 15/4 → 4/1 | note:d s:piano ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "shuffle" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:c s:piano ]",
|
||||
"[ 0/1 → 1/8 | note:e s:piano ]",
|
||||
"[ 1/8 → 1/4 | note:d s:piano ]",
|
||||
"[ 1/4 → 3/8 | note:e s:piano ]",
|
||||
"[ 3/8 → 1/2 | note:f s:piano ]",
|
||||
"[ 1/4 → 3/8 | note:f s:piano ]",
|
||||
"[ 3/8 → 1/2 | note:c s:piano ]",
|
||||
"[ 1/2 → 1/1 | note:g s:piano ]",
|
||||
"[ 1/1 → 9/8 | note:c s:piano ]",
|
||||
"[ 9/8 → 5/4 | note:d s:piano ]",
|
||||
"[ 5/4 → 11/8 | note:e s:piano ]",
|
||||
"[ 11/8 → 3/2 | note:f s:piano ]",
|
||||
"[ 1/1 → 9/8 | note:e s:piano ]",
|
||||
"[ 9/8 → 5/4 | note:c s:piano ]",
|
||||
"[ 5/4 → 11/8 | note:f s:piano ]",
|
||||
"[ 11/8 → 3/2 | note:d s:piano ]",
|
||||
"[ 3/2 → 2/1 | note:g s:piano ]",
|
||||
"[ 2/1 → 17/8 | note:c s:piano ]",
|
||||
"[ 17/8 → 9/4 | note:d s:piano ]",
|
||||
"[ 2/1 → 17/8 | note:d s:piano ]",
|
||||
"[ 17/8 → 9/4 | note:c s:piano ]",
|
||||
"[ 9/4 → 19/8 | note:e s:piano ]",
|
||||
"[ 19/8 → 5/2 | note:f s:piano ]",
|
||||
"[ 5/2 → 3/1 | note:g s:piano ]",
|
||||
"[ 3/1 → 25/8 | note:c s:piano ]",
|
||||
"[ 25/8 → 13/4 | note:d s:piano ]",
|
||||
"[ 13/4 → 27/8 | note:e s:piano ]",
|
||||
"[ 27/8 → 7/2 | note:f s:piano ]",
|
||||
"[ 25/8 → 13/4 | note:e s:piano ]",
|
||||
"[ 13/4 → 27/8 | note:f s:piano ]",
|
||||
"[ 27/8 → 7/2 | note:d s:piano ]",
|
||||
"[ 7/2 → 4/1 | note:g s:piano ]",
|
||||
]
|
||||
`;
|
||||
@@ -10110,6 +10570,420 @@ exports[`runs examples > example "transpose" example index 1 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tremolo" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 1/16 → 1/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 1/8 → 3/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 3/16 → 1/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 1/4 → 5/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 5/16 → 3/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 3/8 → 7/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 7/16 → 1/2 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 1/2 → 9/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 9/16 → 5/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 5/8 → 11/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 11/16 → 3/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 3/4 → 13/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 13/16 → 7/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 7/8 → 15/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 15/16 → 1/1 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 1/1 → 17/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 17/16 → 9/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 9/8 → 19/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 19/16 → 5/4 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 5/4 → 21/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 21/16 → 11/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 11/8 → 23/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 23/16 → 3/2 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 3/2 → 25/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 25/16 → 13/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 13/8 → 27/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 27/16 → 7/4 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 7/4 → 29/16 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 29/16 → 15/8 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 15/8 → 31/16 | note:d# s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 31/16 → 2/1 | note:d s:supersaw tremolo:2 tremoloskew:0.5 ]",
|
||||
"[ 2/1 → 33/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 33/16 → 17/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 17/8 → 35/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 35/16 → 9/4 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 9/4 → 37/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 37/16 → 19/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 19/8 → 39/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 39/16 → 5/2 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 5/2 → 41/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 41/16 → 21/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 21/8 → 43/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 43/16 → 11/4 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 11/4 → 45/16 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 45/16 → 23/8 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 23/8 → 47/16 | note:d# s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 47/16 → 3/1 | note:d s:supersaw tremolo:100 tremoloskew:0.5 ]",
|
||||
"[ 3/1 → 49/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 49/16 → 25/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 25/8 → 51/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 51/16 → 13/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 13/4 → 53/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 53/16 → 27/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 27/8 → 55/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 55/16 → 7/2 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 7/2 → 57/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 57/16 → 29/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 29/8 → 59/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 59/16 → 15/4 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 15/4 → 61/16 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 61/16 → 31/8 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 31/8 → 63/16 | note:d# s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
"[ 63/16 → 4/1 | note:d s:supersaw tremolo:3 tremoloskew:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tremolodepth" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 1/16 → 1/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 1/8 → 3/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 3/16 → 1/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 1/4 → 5/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 5/16 → 3/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 3/8 → 7/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 7/16 → 1/2 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 1/2 → 9/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 9/16 → 5/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 5/8 → 11/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 11/16 → 3/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 3/4 → 13/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 13/16 → 7/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 7/8 → 15/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 15/16 → 1/1 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 1/1 → 17/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 17/16 → 9/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 9/8 → 19/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 19/16 → 5/4 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 5/4 → 21/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 21/16 → 11/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 11/8 → 23/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 23/16 → 3/2 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 3/2 → 25/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 25/16 → 13/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 13/8 → 27/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 27/16 → 7/4 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 7/4 → 29/16 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 29/16 → 15/8 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 15/8 → 31/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 31/16 → 2/1 | note:a1 s:pulse tremolosync:4 tremolodepth:2 ]",
|
||||
"[ 2/1 → 33/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 33/16 → 17/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 17/8 → 35/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 35/16 → 9/4 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 9/4 → 37/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 37/16 → 19/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 19/8 → 39/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 39/16 → 5/2 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 5/2 → 41/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 41/16 → 21/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 21/8 → 43/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 43/16 → 11/4 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 11/4 → 45/16 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 45/16 → 23/8 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 23/8 → 47/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 47/16 → 3/1 | note:a1 s:pulse tremolosync:4 tremolodepth:0.7 ]",
|
||||
"[ 3/1 → 49/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 49/16 → 25/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 25/8 → 51/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 51/16 → 13/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 13/4 → 53/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 53/16 → 27/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 27/8 → 55/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 55/16 → 7/2 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 7/2 → 57/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 57/16 → 29/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 29/8 → 59/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 59/16 → 15/4 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 15/4 → 61/16 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 61/16 → 31/8 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 31/8 → 63/16 | note:a#1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
"[ 63/16 → 4/1 | note:a1 s:pulse tremolosync:4 tremolodepth:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tremolophase" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 1/16 → 1/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 1/8 → 3/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 3/16 → 1/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 1/4 → 5/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 5/16 → 3/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 3/8 → 7/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 7/16 → 1/2 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 1/2 → 9/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 9/16 → 5/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 5/8 → 11/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 11/16 → 3/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 3/4 → 13/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 13/16 → 7/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 7/8 → 15/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 15/16 → 1/1 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 1/1 → 17/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 17/16 → 9/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 9/8 → 19/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 19/16 → 5/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 5/4 → 21/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 21/16 → 11/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 11/8 → 23/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 23/16 → 3/2 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 3/2 → 25/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 25/16 → 13/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 13/8 → 27/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 27/16 → 7/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 7/4 → 29/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 29/16 → 15/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 15/8 → 31/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 31/16 → 2/1 | note:e s:sawtooth tremolosync:4 tremolophase:0.25 ]",
|
||||
"[ 2/1 → 33/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 33/16 → 17/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 17/8 → 35/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 35/16 → 9/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 9/4 → 37/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 37/16 → 19/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 19/8 → 39/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 39/16 → 5/2 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 5/2 → 41/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 41/16 → 21/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 21/8 → 43/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 43/16 → 11/4 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 11/4 → 45/16 | note:f s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 45/16 → 23/8 | note:a s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 23/8 → 47/16 | note:c s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 47/16 → 3/1 | note:e s:sawtooth tremolosync:4 tremolophase:0.66 ]",
|
||||
"[ 3/1 → 49/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 49/16 → 25/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 25/8 → 51/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 51/16 → 13/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 13/4 → 53/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 53/16 → 27/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 27/8 → 55/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 55/16 → 7/2 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 7/2 → 57/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 57/16 → 29/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 29/8 → 59/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 59/16 → 15/4 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 15/4 → 61/16 | note:f s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 61/16 → 31/8 | note:a s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 31/8 → 63/16 | note:c s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
"[ 63/16 → 4/1 | note:e s:sawtooth tremolosync:4 tremolophase:0 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tremoloshape" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 1/16 → 1/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 1/8 → 3/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 3/16 → 1/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 1/4 → 5/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 5/16 → 3/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 3/8 → 7/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 7/16 → 1/2 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 1/2 → 9/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 9/16 → 5/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 5/8 → 11/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 11/16 → 3/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 3/4 → 13/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 13/16 → 7/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 7/8 → 15/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 15/16 → 1/1 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 1/1 → 17/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 17/16 → 9/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 9/8 → 19/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 19/16 → 5/4 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 5/4 → 21/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 21/16 → 11/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 11/8 → 23/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 23/16 → 3/2 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 3/2 → 25/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 25/16 → 13/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 13/8 → 27/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 27/16 → 7/4 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 7/4 → 29/16 | note:f tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 29/16 → 15/8 | note:g tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 15/8 → 31/16 | note:c tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 31/16 → 2/1 | note:d tremolosync:4 tremoloshape:tri s:sawtooth ]",
|
||||
"[ 2/1 → 33/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 33/16 → 17/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 17/8 → 35/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 35/16 → 9/4 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 9/4 → 37/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 37/16 → 19/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 19/8 → 39/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 39/16 → 5/2 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 5/2 → 41/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 41/16 → 21/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 21/8 → 43/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 43/16 → 11/4 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 11/4 → 45/16 | note:f tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 45/16 → 23/8 | note:g tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 23/8 → 47/16 | note:c tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 47/16 → 3/1 | note:d tremolosync:4 tremoloshape:square s:sawtooth ]",
|
||||
"[ 3/1 → 49/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 49/16 → 25/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 25/8 → 51/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 51/16 → 13/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 13/4 → 53/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 53/16 → 27/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 27/8 → 55/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 55/16 → 7/2 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 7/2 → 57/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 57/16 → 29/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 29/8 → 59/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 59/16 → 15/4 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 15/4 → 61/16 | note:f tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 61/16 → 31/8 | note:g tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 31/8 → 63/16 | note:c tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
"[ 63/16 → 4/1 | note:d tremolosync:4 tremoloshape:sine s:sawtooth ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tremoloskew" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 1/16 → 1/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 1/8 → 3/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 3/16 → 1/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 1/4 → 5/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 5/16 → 3/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 3/8 → 7/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 7/16 → 1/2 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 1/2 → 9/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 9/16 → 5/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 5/8 → 11/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 11/16 → 3/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 3/4 → 13/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 13/16 → 7/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 7/8 → 15/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 15/16 → 1/1 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 1/1 → 17/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 17/16 → 9/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 9/8 → 19/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 19/16 → 5/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 5/4 → 21/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 21/16 → 11/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 11/8 → 23/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 23/16 → 3/2 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 3/2 → 25/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 25/16 → 13/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 13/8 → 27/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 27/16 → 7/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 7/4 → 29/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 29/16 → 15/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 15/8 → 31/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 31/16 → 2/1 | note:e s:sawtooth tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 2/1 → 33/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 33/16 → 17/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 17/8 → 35/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 35/16 → 9/4 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 9/4 → 37/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 37/16 → 19/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 19/8 → 39/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 39/16 → 5/2 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 5/2 → 41/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 41/16 → 21/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 21/8 → 43/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 43/16 → 11/4 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 11/4 → 45/16 | note:f s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 45/16 → 23/8 | note:a s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 23/8 → 47/16 | note:c s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 47/16 → 3/1 | note:e s:sawtooth tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 3/1 → 49/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 49/16 → 25/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 25/8 → 51/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 51/16 → 13/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 13/4 → 53/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 53/16 → 27/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 27/8 → 55/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 55/16 → 7/2 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 7/2 → 57/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 57/16 → 29/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 29/8 → 59/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 59/16 → 15/4 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 15/4 → 61/16 | note:f s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 61/16 → 31/8 | note:a s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 31/8 → 63/16 | note:c s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 63/16 → 4/1 | note:e s:sawtooth tremolosync:4 tremoloskew:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tremolosync" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 1/16 → 1/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 1/8 → 3/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 3/16 → 1/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 1/4 → 5/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 5/16 → 3/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 3/8 → 7/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 7/16 → 1/2 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 1/2 → 9/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 9/16 → 5/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 5/8 → 11/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 11/16 → 3/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 3/4 → 13/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 13/16 → 7/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 7/8 → 15/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 15/16 → 1/1 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 1/1 → 17/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 17/16 → 9/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 9/8 → 19/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 19/16 → 5/4 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 5/4 → 21/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 21/16 → 11/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 11/8 → 23/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 23/16 → 3/2 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 3/2 → 25/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 25/16 → 13/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 13/8 → 27/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 27/16 → 7/4 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 7/4 → 29/16 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 29/16 → 15/8 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 15/8 → 31/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 31/16 → 2/1 | note:d s:supersaw tremolosync:4 tremoloskew:0.5 ]",
|
||||
"[ 2/1 → 33/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 33/16 → 17/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 17/8 → 35/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 35/16 → 9/4 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 9/4 → 37/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 37/16 → 19/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 19/8 → 39/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 39/16 → 5/2 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 5/2 → 41/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 41/16 → 21/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 21/8 → 43/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 43/16 → 11/4 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 11/4 → 45/16 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 45/16 → 23/8 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 23/8 → 47/16 | note:d# s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 47/16 → 3/1 | note:d s:supersaw tremolosync:4 tremoloskew:0 ]",
|
||||
"[ 3/1 → 49/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 49/16 → 25/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 25/8 → 51/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 51/16 → 13/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 13/4 → 53/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 53/16 → 27/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 27/8 → 55/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 55/16 → 7/2 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 7/2 → 57/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 57/16 → 29/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 29/8 → 59/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 59/16 → 15/4 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 15/4 → 61/16 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 61/16 → 31/8 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 31/8 → 63/16 | note:d# s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
"[ 63/16 → 4/1 | note:d s:supersaw tremolosync:4 tremoloskew:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tri" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:C3 ]",
|
||||
|
||||
@@ -359,28 +359,6 @@ stack(
|
||||
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
|
||||
).note().piano().slow(2)`;
|
||||
|
||||
/*
|
||||
export const customTrigger = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
stack(
|
||||
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)").layer(x=>x.mul("1.006,.995"))),
|
||||
freq("440(5,8)".clip(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
|
||||
).s("<sawtooth square>/2")
|
||||
.onTrigger((t,hap,ct)=>{
|
||||
const ac = Tone.getContext().rawContext;
|
||||
t = ac.currentTime + t - ct;
|
||||
const { freq, s, gain = 1 } = hap.value;
|
||||
const master = ac.createGain();
|
||||
master.gain.value = 0.1 * gain;
|
||||
master.connect(ac.destination);
|
||||
const o = ac.createOscillator();
|
||||
o.type = s || 'triangle';
|
||||
o.frequency.value = Number(freq);
|
||||
o.connect(master);
|
||||
o.start(t);
|
||||
o.stop(t + hap.duration);
|
||||
}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`; */
|
||||
|
||||
export const swimmingWithSoundfonts = `// Koji Kondo - Swimming (Super Mario World)
|
||||
stack(
|
||||
n(
|
||||
|
||||
@@ -647,7 +647,6 @@
|
||||
"evaluate"
|
||||
],
|
||||
"/packages/webaudio/webaudio.mjs": [
|
||||
"webaudioOutputTrigger",
|
||||
"webaudioOutput",
|
||||
"webaudioRepl"
|
||||
],
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview --port 3009 --host 0.0.0.0",
|
||||
"astro": "astro",
|
||||
"postinstall": "cp node_modules/hs2js/dist/tree-sitter.wasm public && cp node_modules/hs2js/dist/tree-sitter-haskell.wasm public"
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^5.20.0",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"_base": "https://raw.githubusercontent.com/ritchse/tidal-drum-machines/main/machines/EmuSP12/",
|
||||
"bd": ["emusp12-bd/Bassdrum-01.wav","emusp12-bd/Bassdrum-02.wav","emusp12-bd/Bassdrum-03.wav","emusp12-bd/Bassdrum-04.wav","emusp12-bd/Bassdrum-05.wav","emusp12-bd/Bassdrum-06.wav","emusp12-bd/Bassdrum-07.wav","emusp12-bd/Bassdrum-08.wav","emusp12-bd/Bassdrum-09.wav","emusp12-bd/Bassdrum-10.wav","emusp12-bd/Bassdrum-11.wav","emusp12-bd/Bassdrum-12.wav","emusp12-bd/Bassdrum-13.wav","emusp12-bd/Bassdrum-14.wav"],
|
||||
"cb": ["emusp12-cb/Cowbell.wav"],
|
||||
"cp": ["emusp12-cp/Clap.wav"],
|
||||
"cr": ["emusp12-cr/Crash.wav"],
|
||||
"hh": ["emusp12-hh/Hat Closed-01.wav","emusp12-hh/Hat Closed-02.wav"],
|
||||
"ht": ["emusp12-ht/Tom H-01.wav","emusp12-ht/Tom H-02.wav","emusp12-ht/Tom H-03.wav","emusp12-ht/Tom H-04.wav","emusp12-ht/Tom H-05.wav","emusp12-ht/Tom H-06.wav"],
|
||||
"lt": ["emusp12-lt/Tom L-01.wav","emusp12-lt/Tom L-02.wav","emusp12-lt/Tom L-03.wav","emusp12-lt/Tom L-04.wav","emusp12-lt/Tom L-05.wav","emusp12-lt/Tom L-06.wav"],
|
||||
"misc": ["emusp12-misc/Metal-01.wav","emusp12-misc/Metal-02.wav","emusp12-misc/Metal-03.wav","emusp12-misc/Scratch.wav","emusp12-misc/Shot-01.wav","emusp12-misc/Shot-02.wav","emusp12-misc/Shot-03.wav"],
|
||||
"mt": ["emusp12-mt/Tom M-01.wav","emusp12-mt/Tom M-02.wav","emusp12-mt/Tom M-03.wav","emusp12-mt/Tom M-05.wav"],
|
||||
"oh": ["emusp12-oh/Hhopen1.wav"],
|
||||
"perc": ["emusp12-perc/Blow1.wav"],
|
||||
"rd": ["emusp12-rd/Ride.wav"],
|
||||
"rim": ["emusp12-rim/zRim Shot-01.wav","emusp12-rim/zRim Shot-02.wav"],
|
||||
"sd": ["emusp12-sd/Snaredrum-01.wav","emusp12-sd/Snaredrum-02.wav","emusp12-sd/Snaredrum-03.wav","emusp12-sd/Snaredrum-04.wav","emusp12-sd/Snaredrum-05.wav","emusp12-sd/Snaredrum-06.wav","emusp12-sd/Snaredrum-07.wav","emusp12-sd/Snaredrum-08.wav","emusp12-sd/Snaredrum-09.wav","emusp12-sd/Snaredrum-10.wav","emusp12-sd/Snaredrum-11.wav","emusp12-sd/Snaredrum-12.wav","emusp12-sd/Snaredrum-13.wav","emusp12-sd/Snaredrum-14.wav","emusp12-sd/Snaredrum-15.wav","emusp12-sd/Snaredrum-16.wav","emusp12-sd/Snaredrum-17.wav","emusp12-sd/Snaredrum-18.wav","emusp12-sd/Snaredrum-19.wav","emusp12-sd/Snaredrum-20.wav","emusp12-sd/Snaredrum-21.wav"]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"_base": "https://raw.githubusercontent.com/tidalcycles/uzu-drumkit/main/",
|
||||
"bd": [
|
||||
"bd/10_bd_switchangel.wav",
|
||||
"bd/11_bd_mot4i.wav",
|
||||
"bd/12_bd_mot4i.wav",
|
||||
"bd/13_bd_mot4i.wav",
|
||||
"bd/14_bd_switchangel.wav",
|
||||
"bd/15_bd_switchangel.wav",
|
||||
"bd/16_bd_switchangel.wav",
|
||||
"bd/17_bd_switchangel.wav"
|
||||
],
|
||||
"brk": [
|
||||
"brk/10_break_amen_pprocessed.wav"
|
||||
],
|
||||
"cb": [
|
||||
"cb/10_perc_switchangel.wav"
|
||||
],
|
||||
"cp": [
|
||||
"cp/10_cp_switchangel.wav",
|
||||
"cp/11_cp_mot4i.wav"
|
||||
],
|
||||
"cr": [
|
||||
"cr/10_cr_switchangel.wav",
|
||||
"cr/11_cr_mot4i.wav"
|
||||
],
|
||||
"hh": [
|
||||
"hh/10_hh_switchangel.wav",
|
||||
"hh/11_hh_mot4i.wav",
|
||||
"hh/12_hh_switchangel.wav",
|
||||
"hh/13_hh_switchangel.wav",
|
||||
"hh/14_hh_mot4i.wav"
|
||||
],
|
||||
"ht": [
|
||||
"ht/10_ht_mot4i.wav"
|
||||
],
|
||||
"lt": [
|
||||
"lt/10_lt_mot4i.wav"
|
||||
],
|
||||
"misc": [
|
||||
"misc/10_misc_switchangel_ludens.wav",
|
||||
"misc/11_misc_switchangel_ludens.wav",
|
||||
"misc/12_misc_switchangel_ludens.wav",
|
||||
"misc/13_misc_switchangel_ludens.wav",
|
||||
"misc/14_misc_switchangel_ludens.wav"
|
||||
],
|
||||
"mt": [
|
||||
"mt/10_mt_mot4i.wav"
|
||||
],
|
||||
"oh": [
|
||||
"oh/10_oh_switchangel.wav",
|
||||
"oh/11_oh_switchangel.wav",
|
||||
"oh/12_oh_switchangel.wav",
|
||||
"oh/13_oh_switchangel.wav"
|
||||
],
|
||||
"rd": [
|
||||
"rd/10_rd_switchangel.wav"
|
||||
],
|
||||
"rim": [
|
||||
"rim/10_rim_switchangel.wav",
|
||||
"rim/11_rim_switch_angel.wav"
|
||||
],
|
||||
"sd": [
|
||||
"sd/10_sd_switchangel-bounce-2.wav",
|
||||
"sd/11_sd_switchangel_3.wav",
|
||||
"sd/12_sd_switchangel_2.wav",
|
||||
"sd/13_sd_switchangel_2.wav",
|
||||
"sd/14_sd.wav"
|
||||
],
|
||||
"sh": [
|
||||
"sh/10_sh_switchangel.wav"
|
||||
],
|
||||
"tb": [
|
||||
"tb/10_tb.wav"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
---
|
||||
title: 'Release Notes v1.1.0'
|
||||
description: ''
|
||||
date: '2024-06-02'
|
||||
tags: ['meta']
|
||||
author: froos
|
||||
---
|
||||
|
||||
These are the release notes for Strudel 1.1.0 aka "Bananensplit".
|
||||
|
||||
The last release was over 19 weeks ago, so a lot of things have happened!
|
||||
|
||||
First, here's a little demo, teasing some of the new features:
|
||||
|
||||
import { Youtube } from '@src/components/Youtube';
|
||||
|
||||
<Youtube client:only="react" id="2Qu5lMHruio" />
|
||||
|
||||
Let's write up some of the highlights:
|
||||
|
||||
## New DSP Features
|
||||
|
||||
### Stereo Supersaw
|
||||
|
||||
with spread, unison, and detune parameters
|
||||
|
||||
```plaintext
|
||||
note("d f a a# a d3").fast(2)
|
||||
.s("supersaw").spread(".8").detune(.3).unison("2 7")
|
||||
```
|
||||
|
||||
### Analog "ladder" filter type
|
||||
|
||||
works great for acid basslines and vibey tones
|
||||
|
||||
```plaintext
|
||||
note("{d d d a a# d3 f4}%16".sub(12)).gain(1).s("sawtooth")
|
||||
.lpf(200).lpenv(slider(1.36,0,8)).lpq(7).distort("1.5:.7")`
|
||||
.ftype('ladder')
|
||||
```
|
||||
|
||||
### stereo distortion effect
|
||||
|
||||
```plaintext
|
||||
note("{g g a# g g4}%8".add("{0 7 12 0}%8")).lpf(500)
|
||||
.s("supersaw").dist("4:.2")
|
||||
```
|
||||
|
||||
## Editor Features
|
||||
|
||||
### inline viz
|
||||
|
||||
The editor now supports multiple visuals within the code, using the `_` prefix for viz functions:
|
||||
|
||||
<img
|
||||
width="595"
|
||||
alt="Screenshot 2024-06-01 at 01 23 51"
|
||||
src="https://codeberg.org/uzu/strudel/assets/12023032/978fee85-e533-4da6-a245-d20d0083f57e"
|
||||
/>
|
||||
|
||||
- `._pianoroll()`: inline pianoroll
|
||||
- `._punchcard()`: inline punchcard
|
||||
- `._scope()`: inline scope
|
||||
- `._pitchwheel()`: inline pitchwheel
|
||||
|
||||
For more info, check out the new [Visual Feedback Page](https://strudel.cc/learn/visual-feedback/)
|
||||
|
||||
### label notation
|
||||
|
||||
This new notation simplifies writing patterns at the top level:
|
||||
|
||||
```plaintext
|
||||
d1: s("bd*4")
|
||||
d2: s("[- hh]*4")
|
||||
```
|
||||
|
||||
This is equivalent to:
|
||||
|
||||
```plaintext
|
||||
stack(
|
||||
s("bd*4"),
|
||||
s("[- hh]*4")
|
||||
)
|
||||
```
|
||||
|
||||
The labels you choose are arbitrary, the above `d1` and `d2` are a typical thing you'd write in tidal, for example `d1 $ s "bd*4"`.
|
||||
If the same label is used multiple times, the last one wins:
|
||||
|
||||
```plaintext
|
||||
d1: s("bd*4")
|
||||
d1: s("[- hh]*4") // <-- only this plays
|
||||
```
|
||||
|
||||
There is a special label anonymous label `$`, which can appear multiple times without overriding itself:
|
||||
|
||||
```plaintext
|
||||
// both of these will play:
|
||||
$: s("bd*4")
|
||||
$: s("[- hh]*4")
|
||||
```
|
||||
|
||||
You can mute a pattern by prefixing `_`:
|
||||
|
||||
```plaintext
|
||||
_$: s("bd*4") // <-- this one is muted
|
||||
$: s("[- hh]*4")
|
||||
```
|
||||
|
||||
To run a transformation on all patterns, you can use `all`:
|
||||
|
||||
```plaintext
|
||||
$: s("bd*4")
|
||||
$: s("[- hh]*4")
|
||||
all(x=>x.room(.5))
|
||||
```
|
||||
|
||||
This notation is now the recommended way to [play patterns in parallel](https://strudel.cc/workshop/first-notes/#playing-multiple-patterns)
|
||||
|
||||
### Clock sync between multiple instances
|
||||
|
||||
timing has received a major overhaul, and is now much more accurate on all browsers. Additionally, you can now sync timing across multiple windows.
|
||||

|
||||
|
||||
### Better sample upload support
|
||||
|
||||
you can now upload large amounts of samples much faster across all browsers including on IOS devices. supported filetypes now include: ogg flac mp3 wav aac m4a
|
||||
|
||||
### experimental tidal syntax
|
||||
|
||||
The new `tidal` function allows you to write strudel patterns in tidal syntax:
|
||||
|
||||
```plaintext
|
||||
await initTidal()
|
||||
|
||||
tidal`
|
||||
d1 $ s "bd*4"
|
||||
|
||||
d2 $ s "[- hh]*4"
|
||||
`
|
||||
```
|
||||
|
||||
As we're looking to improve compatibility with tidal, we're happy to hear feedback.
|
||||
|
||||
## breaking changes
|
||||
|
||||
This release comes with a bunch of breaking changes. If you find your patterns to sound different, check out the PRs below for guidance on how to update them. Most of these changes shouldn't affect a lot of patterns.
|
||||
In case of doubt, add the line `// @version 1.0` to your old pattern.
|
||||
If you're having problems, please let us know!
|
||||
|
||||
- remove legacy legato + duration implementations by @felixroos in https://codeberg.org/uzu/strudel/pull/965
|
||||
- Velocity in value by @felixroos in https://codeberg.org/uzu/strudel/pull/974
|
||||
- use ireal as default voicing dict by @felixroos https://codeberg.org/uzu/strudel/pull/967
|
||||
- Color in hap value by @felixroos https://codeberg.org/uzu/strudel/pull/1007
|
||||
- rename trig -> reset, trigzero -> restart by @felixroos https://codeberg.org/uzu/strudel/pull/1010
|
||||
- remove dangerous arithmetic feature by @felixroos https://codeberg.org/uzu/strudel/pull/1030
|
||||
- change fanchor to 0 by @daslyfe https://codeberg.org/uzu/strudel/pull/1107
|
||||
|
||||
## superdough features
|
||||
|
||||
- replace shape with distort in learn doc by @daslyfe https://codeberg.org/uzu/strudel/pull/982
|
||||
- Worklet Improvents / fixes by @daslyfe https://codeberg.org/uzu/strudel/pull/963
|
||||
- supersaw oscillator by @daslyfe https://codeberg.org/uzu/strudel/pull/978
|
||||
- Add analog-style ladder filter by @daslyfe https://codeberg.org/uzu/strudel/pull/1103
|
||||
- Calculate phaser modulation phase based on time by @daslyfe https://codeberg.org/uzu/strudel/pull/1110
|
||||
- rollback phaser by @daslyfe https://codeberg.org/uzu/strudel/pull/1113
|
||||
|
||||
## editor / ui features
|
||||
|
||||
- 'Enable Bracket Matching' option in Codemirror by @eefano https://codeberg.org/uzu/strudel/pull/956
|
||||
- REPL sync between windows by @daslyfe https://codeberg.org/uzu/strudel/pull/900
|
||||
- inline viz / widgets package by @felixroos https://codeberg.org/uzu/strudel/pull/989
|
||||
- Inline punchcard + spiral by @felixroos https://codeberg.org/uzu/strudel/pull/1008
|
||||
- More fonts by @felixroos https://codeberg.org/uzu/strudel/pull/1023
|
||||
- better theme integration for visuals + various fixes by @felixroos https://codeberg.org/uzu/strudel/pull/1024
|
||||
- add setting for sync flag by @felixroos https://codeberg.org/uzu/strudel/pull/1025
|
||||
- add closeBrackets setting by @felixroos https://codeberg.org/uzu/strudel/pull/1031
|
||||
- add font file types to offline cache by @felixroos https://codeberg.org/uzu/strudel/pull/1032
|
||||
- pitchwheel visual by @felixroos https://codeberg.org/uzu/strudel/pull/1041
|
||||
- repl: set document.title from @title by @kasparsj https://codeberg.org/uzu/strudel/pull/1090
|
||||
- Samples tab improvements by @daslyfe https://codeberg.org/uzu/strudel/pull/1102
|
||||
|
||||
## language features
|
||||
|
||||
- pickOut(), pickRestart(), pickReset() by @eefano https://codeberg.org/uzu/strudel/pull/950
|
||||
- Auto await samples by @felixroos https://codeberg.org/uzu/strudel/pull/955
|
||||
- feat: can now invert euclid pulses with negative numbers by @felixroos https://codeberg.org/uzu/strudel/pull/959
|
||||
- Nested controls by @felixroos https://codeberg.org/uzu/strudel/pull/973
|
||||
- alias - for ~ by @yaxu https://codeberg.org/uzu/strudel/pull/981
|
||||
- Beat-oriented functionality by @yaxu https://codeberg.org/uzu/strudel/pull/976
|
||||
- Labeled statements by @felixroos https://codeberg.org/uzu/strudel/pull/991
|
||||
- accidentals in scale degrees by @eefano https://codeberg.org/uzu/strudel/pull/1000
|
||||
- Feature: tactus marking by @yaxu https://codeberg.org/uzu/strudel/pull/1021
|
||||
- Tactus tidy by @yaxu https://codeberg.org/uzu/strudel/pull/1027
|
||||
- Wax, wane, taper and taperlist by @yaxu https://codeberg.org/uzu/strudel/pull/1042
|
||||
- transpose: support all combinations of numbers and strings for notes and intervals by @felixroos https://codeberg.org/uzu/strudel/pull/1048
|
||||
- anonymous patterns + muting by @felixroos https://codeberg.org/uzu/strudel/pull/1059
|
||||
- add swing + swingBy by @felixroos https://codeberg.org/uzu/strudel/pull/1038
|
||||
- Stepwise functions from Tidal by @yaxu https://codeberg.org/uzu/strudel/pull/1060
|
||||
- Tactus tweaks - fixes for maintaining tactus and highlight locations by @yaxu https://codeberg.org/uzu/strudel/pull/1065
|
||||
- Fix stepjoin by @yaxu https://codeberg.org/uzu/strudel/pull/1067
|
||||
- More tactus tidying by @yaxu https://codeberg.org/uzu/strudel/pull/1071
|
||||
- Tactus calculation toggle and breaking change to tactus calculation in fast/slow/hurry by @yaxu https://codeberg.org/uzu/strudel/pull/1081
|
||||
- hs2js package / tidal parser by @felixroos https://codeberg.org/uzu/strudel/pull/870
|
||||
- Add the mousex and mousey signal by @Enelg52 https://codeberg.org/uzu/strudel/pull/1112
|
||||
- can now access strudelMirror from repl by @felixroos https://codeberg.org/uzu/strudel/pull/1117
|
||||
|
||||
## sampler
|
||||
|
||||
If you have nodejs installed on your system, you can now use [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) to serve samples from disk to the REPL or flok.
|
||||
|
||||
- local sample server cli by @felixroos https://codeberg.org/uzu/strudel/pull/1033
|
||||
- Fix sampler paths by @felixroos https://codeberg.org/uzu/strudel/pull/1034
|
||||
- Fix sampler windows by @felixroos https://codeberg.org/uzu/strudel/pull/1108
|
||||
- fix sampler on windows by @geikha https://codeberg.org/uzu/strudel/pull/1109
|
||||
|
||||
## docs
|
||||
|
||||
- V1 release notes by @felixroos https://codeberg.org/uzu/strudel/pull/935
|
||||
- Minor documentation error: Update first-sounds.mdx by @mhetrick https://codeberg.org/uzu/strudel/pull/941
|
||||
- Update synths.mdx by @andresgottlieb https://codeberg.org/uzu/strudel/pull/984
|
||||
- using strudel in your project guide + cleanup examples by @felixroos https://codeberg.org/uzu/strudel/pull/1006
|
||||
- Document signals by @ilesinge https://codeberg.org/uzu/strudel/pull/1015
|
||||
- improve tutorial + custom samples doc by @felixroos https://codeberg.org/uzu/strudel/pull/1053
|
||||
- fix cr typo on first-sounds.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1068
|
||||
- fix first sounds typo by @cleary https://codeberg.org/uzu/strudel/pull/1069
|
||||
- add `<...>` to first-sounds.mdx recap by @cleary https://codeberg.org/uzu/strudel/pull/1070
|
||||
- add nesting to `off` example variation in pattern-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1075
|
||||
- fix translation issue in first-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1072
|
||||
- add signals to recap in first-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1073
|
||||
- fix docs on alignment.mdx by @diegodorado https://codeberg.org/uzu/strudel/pull/1076
|
||||
- fix little dub tune example by @lukad https://codeberg.org/uzu/strudel/pull/1104
|
||||
- clarify `off` in pattern-effects.mdx by @cleary https://codeberg.org/uzu/strudel/pull/1074
|
||||
- Fixes drawPianoroll import in codemirror example by @giohappy https://codeberg.org/uzu/strudel/pull/1116
|
||||
- Migrate tutorial fanchor by @felixroos https://codeberg.org/uzu/strudel/pull/1122
|
||||
|
||||
## internals
|
||||
|
||||
- remove cjs builds by @felixroos https://codeberg.org/uzu/strudel/pull/945
|
||||
- controls refactoring: simplify exports by @felixroos https://codeberg.org/uzu/strudel/pull/962
|
||||
- move canvas related helpers from core to new draw package by @felixroos https://codeberg.org/uzu/strudel/pull/971
|
||||
- remove canvas, externalize samples, delete junk by @felixroos https://codeberg.org/uzu/strudel/pull/1003
|
||||
- Improve performance of ! (replicate) by @yaxu https://codeberg.org/uzu/strudel/pull/1084
|
||||
- Benchmarks by @yaxu https://codeberg.org/uzu/strudel/pull/1079
|
||||
|
||||
## fixes
|
||||
|
||||
- fix midi issue on firefox and added quote error by @Enelg52 https://codeberg.org/uzu/strudel/pull/936
|
||||
- fix: pianoroll sorting by @felixroos https://codeberg.org/uzu/strudel/pull/938
|
||||
- account for cps in midi time duration by @daslyfe https://codeberg.org/uzu/strudel/pull/954
|
||||
- fix script importable packages (web + repl) by @felixroos https://codeberg.org/uzu/strudel/pull/957
|
||||
- fix: reset global fx on pattern change by @felixroos https://codeberg.org/uzu/strudel/pull/960
|
||||
- add debounce to logger by @felixroos https://codeberg.org/uzu/strudel/pull/968
|
||||
- fix for transpose(): preserve hap value object structure by @eefano https://codeberg.org/uzu/strudel/pull/966
|
||||
- fix: clear hydra on reset by @felixroos https://codeberg.org/uzu/strudel/pull/983
|
||||
- little fix for withVal by @eefano https://codeberg.org/uzu/strudel/pull/980
|
||||
- fix: share now shares what's visible instead of active by @felixroos https://codeberg.org/uzu/strudel/pull/985
|
||||
- Fix pure mini highlight by @yaxu https://codeberg.org/uzu/strudel/pull/994
|
||||
- fix: await injectPatternMethods by @felixroos https://codeberg.org/uzu/strudel/pull/1012
|
||||
- update undocumented script by @felixroos https://codeberg.org/uzu/strudel/pull/1013
|
||||
- eliminate chromium clock jitter by @felixroos https://codeberg.org/uzu/strudel/pull/1004
|
||||
- Repl sync fixes by @daslyfe https://codeberg.org/uzu/strudel/pull/1014
|
||||
- hotfix for 1017 by @daslyfe https://codeberg.org/uzu/strudel/pull/1020
|
||||
- fix cyclist fizzling out by @felixroos https://codeberg.org/uzu/strudel/pull/1046
|
||||
- Midi Time hotfix for scheduler updates by @daslyfe https://codeberg.org/uzu/strudel/pull/1047
|
||||
- fix: do not reset cc input values on each eval by @felixroos https://codeberg.org/uzu/strudel/pull/1054
|
||||
- Fix wchooseCycles not picking the whole pattern by @ilesinge https://codeberg.org/uzu/strudel/pull/1061
|
||||
- fix OSC timing for recent scheduler updates by @daslyfe https://codeberg.org/uzu/strudel/pull/1062
|
||||
- clarify license by @yaxu https://codeberg.org/uzu/strudel/pull/1064
|
||||
- fix failing format test by @daslyfe https://codeberg.org/uzu/strudel/pull/1077
|
||||
- fix: url parsing with extra params by @felixroos https://codeberg.org/uzu/strudel/pull/1083
|
||||
- fix: csound + dough timing by @felixroos https://codeberg.org/uzu/strudel/pull/1086
|
||||
- fix: missing events due to premature worklet cleanup by @felixroos https://codeberg.org/uzu/strudel/pull/1089
|
||||
- Use sessionStorage for viewingPatternData and activePattern by @kasparsj https://codeberg.org/uzu/strudel/pull/1091
|
||||
- osc: couple of fixes by @kasparsj https://codeberg.org/uzu/strudel/pull/1093
|
||||
- web package fixes by @felixroos https://codeberg.org/uzu/strudel/pull/1044
|
||||
- Fix audio worklets by @daslyfe https://codeberg.org/uzu/strudel/pull/1114
|
||||
- fix: use full repl in web package by @felixroos https://codeberg.org/uzu/strudel/pull/1119
|
||||
- [BUG FIX] Audio worklets sometimes dont load by @daslyfe https://codeberg.org/uzu/strudel/pull/1121
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @mhetrick made their first contribution https://codeberg.org/uzu/strudel/pull/941
|
||||
- @eefano made their first contribution https://codeberg.org/uzu/strudel/pull/956
|
||||
- @Enelg52 made their first contribution https://codeberg.org/uzu/strudel/pull/936
|
||||
- @andresgottlieb made their first contribution https://codeberg.org/uzu/strudel/pull/984
|
||||
- @cleary made their first contribution https://codeberg.org/uzu/strudel/pull/1068
|
||||
- @diegodorado made their first contribution https://codeberg.org/uzu/strudel/pull/1076
|
||||
- @lukad made their first contribution https://codeberg.org/uzu/strudel/pull/1104
|
||||
- @giohappy made their first contribution https://codeberg.org/uzu/strudel/pull/1116
|
||||
|
||||
A huge thanks to all contributors!!!
|
||||
|
||||
## Packages
|
||||
|
||||
- @strudel/codemirror@1.1.0
|
||||
- @strudel/core@1.1.0
|
||||
- @strudel/csound@1.1.0
|
||||
- @strudel/draw@1.1.0
|
||||
- @strudel/embed@1.1.0
|
||||
- hs2js@0.1.0
|
||||
- @strudel/hydra@1.1.0
|
||||
- @strudel/midi@1.1.0
|
||||
- @strudel/mini@1.1.0
|
||||
- @strudel/osc@1.1.0
|
||||
- @strudel/repl@1.1.0
|
||||
- @strudel/sampler@0.1.0
|
||||
- @strudel/serial@1.1.0
|
||||
- @strudel/soundfonts@1.1.0
|
||||
- superdough@1.1.0
|
||||
- @strudel/tidal@0.1.0
|
||||
- @strudel/tonal@1.1.0
|
||||
- @strudel/transpiler@1.1.0
|
||||
- @strudel/web@1.1.0
|
||||
- @strudel/webaudio@1.1.0
|
||||
- @strudel/xen@1.1.0
|
||||
|
||||
**Full Changelog**: https://codeberg.org/uzu/strudel/compare/v1.0.0...v1.1.0
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
title: 'Release Notes v1.2.0'
|
||||
description: ''
|
||||
date: '2025-05-01'
|
||||
tags: ['meta']
|
||||
author: froos
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
## highlights
|
||||
|
||||
- [stepwise functions](https://strudel.cc/learn/stepwise/) ([PR](https://github.com/tidalcycles/strudel/pull/1262))
|
||||
- [midimaps](https://strudel.cc/learn/input-output/#midimaps) ([PR](https://github.com/tidalcycles/strudel/pull/1274))
|
||||
- [spectrum](https://strudel.cc/learn/visual-feedback/#spectrum) ([PR](https://github.com/tidalcycles/strudel/pull/1213))
|
||||
- [mqtt](https://strudel.cc/learn/input-output/#mqtt) ([PR](https://github.com/tidalcycles/strudel/pull/1224))
|
||||
- pulse oscillator (todo: https://github.com/tidalcycles/strudel/issues/1336) ([PR](https://github.com/tidalcycles/strudel/pull/1304))
|
||||
- theme improvements
|
||||
|
||||
## breaking changes
|
||||
|
||||
- [breaking change] Sample signals from query onset, rather than midpoint by @yaxu in https://github.com/tidalcycles/strudel/pull/1278
|
||||
- change behaviour of polymeter, and remove polymeterSteps by @yaxu in https://github.com/tidalcycles/strudel/pull/1302
|
||||
- Polish, rename, and document stepwise functions by @yaxu in https://github.com/tidalcycles/strudel/pull/1262
|
||||
|
||||
### superdough
|
||||
|
||||
- feat: Create Pulse Oscillator with variable PWM by @daslyfe in https://github.com/tidalcycles/strudel/pull/1304
|
||||
- add num samples (edited numbers) by @yaxu in https://github.com/tidalcycles/strudel/pull/1309
|
||||
- Add num samples from 0 up to 20 by @yaxu in https://github.com/tidalcycles/strudel/pull/1310
|
||||
- feat: add max polyphony feature for superdough by @daslyfe in https://github.com/tidalcycles/strudel/pull/1317
|
||||
|
||||
### docs
|
||||
|
||||
- doc: visual functions + refactor onPaint by @felixroos in https://github.com/tidalcycles/strudel/pull/1125
|
||||
- Labeled statements doc by @felixroos in https://github.com/tidalcycles/strudel/pull/1126
|
||||
- Correct spelling mistakes by @EdwardBetts in https://github.com/tidalcycles/strudel/pull/1183
|
||||
- remove redundant example for cat, update snapshot by @kdiab in https://github.com/tidalcycles/strudel/pull/1189
|
||||
- chore: Edit run locally instructions in README.md by @ChinoUkaegbu in https://github.com/tidalcycles/strudel/pull/1206
|
||||
- suggested changes to voicings.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/1231
|
||||
- Documentation for all/each, and bugfix for each by @yaxu in https://github.com/tidalcycles/strudel/pull/1233
|
||||
- Update documentation for param value modification by @gillespi314 in https://github.com/tidalcycles/strudel/pull/1238
|
||||
- fix docs for beat function by @daslyfe in https://github.com/tidalcycles/strudel/pull/1248
|
||||
- understand voicings page by @felixroos in https://github.com/tidalcycles/strudel/pull/1230
|
||||
- add reference package by @felixroos in https://github.com/tidalcycles/strudel/pull/1252
|
||||
- Stepwise documentation tweaks, with mridangam samples by @yaxu in https://github.com/tidalcycles/strudel/pull/1275
|
||||
- showcase tweaks by @yaxu in https://github.com/tidalcycles/strudel/pull/1291
|
||||
- Signpost licenses for source code and samples a bit more, ref #1277 by @yaxu in https://github.com/tidalcycles/strudel/pull/1289
|
||||
- Fix misplaced ending sentence by @makmanalp in https://github.com/tidalcycles/strudel/pull/1296
|
||||
- Fix typo pattnr by @ReneNyffenegger in https://github.com/tidalcycles/strudel/pull/1316
|
||||
- update docs to reflect import sounds tab change by @hpunq in https://github.com/tidalcycles/strudel/pull/1332
|
||||
|
||||
### ui improvements
|
||||
|
||||
- Udels (MultiFrame Strudel) Revisited by @daslyfe in https://github.com/tidalcycles/strudel/pull/1132
|
||||
- Create audio target selector for OSC/Superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1160
|
||||
- Add a search bar to the REPL Reference tab by @netux in https://github.com/tidalcycles/strudel/pull/1165
|
||||
- Adding search bar (soundtab.jsx) by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/1185
|
||||
- add 2 new ui settings by @felixroos in https://github.com/tidalcycles/strudel/pull/1200
|
||||
- Theme glowup by @felixroos in https://github.com/tidalcycles/strudel/pull/1268
|
||||
- Create Pattern Page Pagination by @daslyfe in https://github.com/tidalcycles/strudel/pull/1287
|
||||
- feat: Theme improvements by @daslyfe in https://github.com/tidalcycles/strudel/pull/1295
|
||||
- feat: new themes + theme improvements by @daslyfe in https://github.com/tidalcycles/strudel/pull/1326
|
||||
- Add new "import-sounds" tab with explanation on folder import by @hpunq in https://github.com/tidalcycles/strudel/pull/1329
|
||||
- Add Icon to import sample button by @daslyfe in https://github.com/tidalcycles/strudel/pull/1331
|
||||
- better spacing in zen mode by @felixroos in https://github.com/tidalcycles/strudel/pull/1147
|
||||
- Screenreader improvements by @yaxu in https://github.com/tidalcycles/strudel/pull/1158
|
||||
- colorize console + tweak header by @felixroos in https://github.com/tidalcycles/strudel/pull/1203
|
||||
- Menu Panel Improvements! by @daslyfe in https://github.com/tidalcycles/strudel/pull/1193
|
||||
- Make panel hover behavior optional by @daslyfe in https://github.com/tidalcycles/strudel/pull/1199
|
||||
- REPL: solo and sync configuration by @bthj in https://github.com/tidalcycles/strudel/pull/1214
|
||||
- enhancement: make error messages easier to read by @daslyfe in https://github.com/tidalcycles/strudel/pull/1315
|
||||
|
||||
### mqtt
|
||||
|
||||
- MQTT support by @yaxu in https://github.com/tidalcycles/strudel/pull/1224
|
||||
- MQTT - if password isn't provided, prompt for one by @yaxu in https://github.com/tidalcycles/strudel/pull/1249
|
||||
- MQTT - support adding hap duration and cps metadata to JSON messages by @yaxu in https://github.com/tidalcycles/strudel/pull/1279
|
||||
- make mqtt topic patternable by @yaxu in https://github.com/tidalcycles/strudel/pull/1280
|
||||
- Bugfix: update mqtt connections dictionary by @yaxu in https://github.com/tidalcycles/strudel/pull/1281
|
||||
- mqtt bugfix - connection check by @yaxu in https://github.com/tidalcycles/strudel/pull/1282
|
||||
|
||||
### new functions
|
||||
|
||||
- Add scramble and shuffle by @yaxu in https://github.com/tidalcycles/strudel/pull/1167
|
||||
- polyJoin by @yaxu in https://github.com/tidalcycles/strudel/pull/1168
|
||||
- Add seqPLoop from Tidal by @yaxu in https://github.com/tidalcycles/strudel/pull/1182
|
||||
- add filter + filterWhen + within by @felixroos in https://github.com/tidalcycles/strudel/pull/1039
|
||||
- Add bite function by @yaxu in https://github.com/tidalcycles/strudel/pull/1187
|
||||
- markcss by @felixroos in https://github.com/tidalcycles/strudel/pull/1202
|
||||
- "beat" function for "step sequencer" style rhythm notation by @daslyfe in https://github.com/tidalcycles/strudel/pull/1237
|
||||
- Add s_zip for 'cat'-ing patterns together step-by-step, bugfix `steps` by @yaxu in https://github.com/tidalcycles/strudel/pull/1208
|
||||
- "stretch" function (phase vocoder) by @daslyfe in https://github.com/tidalcycles/strudel/pull/1130
|
||||
- add basic spectrum function by @felixroos in https://github.com/tidalcycles/strudel/pull/1213
|
||||
- Add onKey function for custom key commands for patterns by @daslyfe in https://github.com/tidalcycles/strudel/pull/1235
|
||||
- Add binary and binaryN by @heerman in https://github.com/tidalcycles/strudel/pull/1226
|
||||
- midimaps by @felixroos in https://github.com/tidalcycles/strudel/pull/1274
|
||||
- small feat: Add alias for segment and ribbon by @daslyfe in https://github.com/tidalcycles/strudel/pull/1314
|
||||
- feat: Create scrub function for scrubbing an audio file by @daslyfe in https://github.com/tidalcycles/strudel/pull/1321
|
||||
- feat: Improve gain curve by @daslyfe in https://github.com/tidalcycles/strudel/pull/1318
|
||||
- Chop chop by @yaxu in https://github.com/tidalcycles/strudel/pull/1078
|
||||
|
||||
### more
|
||||
|
||||
- Make `all()` post-stack again, and add `each()` for pre-stack by @yaxu in https://github.com/tidalcycles/strudel/pull/1229
|
||||
- Add stepBind, and some toplevel aliases for binds and withValue by @yaxu in https://github.com/tidalcycles/strudel/pull/1241
|
||||
- Make cps patternable by @eefano in https://github.com/tidalcycles/strudel/pull/1001
|
||||
- Allow wchooseCycles probabilities to be patterned by @yaxu in https://github.com/tidalcycles/strudel/pull/1292
|
||||
- @strudel/sampler improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/1288
|
||||
|
||||
### refactor
|
||||
|
||||
- expose comment commands by @felixroos in https://github.com/tidalcycles/strudel/pull/1136
|
||||
- containerize/seperate out boolean checks for repl types/Repl logic into bespoke components. by @daslyfe in https://github.com/tidalcycles/strudel/pull/1163
|
||||
- Improve + simplify neocyclist timing by @daslyfe in https://github.com/tidalcycles/strudel/pull/1164
|
||||
- Make phaser control consistent with superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1178
|
||||
- Revert "Make phaser control consistent with superdirt" by @daslyfe in https://github.com/tidalcycles/strudel/pull/1179
|
||||
- make phaser control match superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1180
|
||||
- refactor sampler by @felixroos in https://github.com/tidalcycles/strudel/pull/1101
|
||||
- update lockfile + minor versions by @felixroos in https://github.com/tidalcycles/strudel/pull/1198
|
||||
- Preserve tactus for 'degrade' and friends, and tidy up 'pick' and friends by @yaxu in https://github.com/tidalcycles/strudel/pull/1205
|
||||
- Apply `all` function to individual patterns rather than final stack by @yaxu in https://github.com/tidalcycles/strudel/pull/1209
|
||||
- Revert "Fix sometimes" by @yaxu in https://github.com/tidalcycles/strudel/pull/1267
|
||||
- patchday by @felixroos in https://github.com/tidalcycles/strudel/pull/1264
|
||||
- Rename repeat back to extend by @yaxu in https://github.com/tidalcycles/strudel/pull/1285
|
||||
- Send delta in OSC message in seconds, to match tidal/superdirt by @yaxu in https://github.com/tidalcycles/strudel/pull/1323
|
||||
|
||||
### fixes
|
||||
|
||||
- Fix clock worker dependency path in module builds by @matthewkaney in https://github.com/tidalcycles/strudel/pull/1129
|
||||
- Fix bug in Fraction.lcm by @yaxu in https://github.com/tidalcycles/strudel/pull/1133
|
||||
- Fix tactus marking in mininotation by @yaxu in https://github.com/tidalcycles/strudel/pull/1144
|
||||
- Fix loopAt tactus by @yaxu in https://github.com/tidalcycles/strudel/pull/1145
|
||||
- Fix OSC clock jitter by @daslyfe in https://github.com/tidalcycles/strudel/pull/1157
|
||||
- [CORS HOTFIX] by @daslyfe in https://github.com/tidalcycles/strudel/pull/1162
|
||||
- Fixes fit so it works after a chop or slice by @yaxu in https://github.com/tidalcycles/strudel/pull/1171
|
||||
- fix sample speed when using splice and fit with superdirt by @daslyfe in https://github.com/tidalcycles/strudel/pull/1172
|
||||
- handle midin device not found error by @felixroos in https://github.com/tidalcycles/strudel/pull/1146
|
||||
- Fix serial timing by @yaxu in https://github.com/tidalcycles/strudel/pull/1188
|
||||
- Fix regression for d1, p1, p(n) by @yaxu in https://github.com/tidalcycles/strudel/pull/1227
|
||||
- Fix sometimes by @yaxu in https://github.com/tidalcycles/strudel/pull/1243
|
||||
- Fix sf2 timing by @felixroos in https://github.com/tidalcycles/strudel/pull/1272
|
||||
- Fix "squeezejoin" and functions using it, including "bite" by @yaxu in https://github.com/tidalcycles/strudel/pull/1286
|
||||
- Fixes inverted triangle wave by renaming it to "itri", making non-inverted "tri" by @yaxu in https://github.com/tidalcycles/strudel/pull/1283
|
||||
- Hotfix: prevent undefined pattern code from crashing strudel on load by @daslyfe in https://github.com/tidalcycles/strudel/pull/1297
|
||||
- Fix test error #1297 by @nkymut in https://github.com/tidalcycles/strudel/pull/1298
|
||||
- bugfix zoom stepcount by @yaxu in https://github.com/tidalcycles/strudel/pull/1301
|
||||
- bugfix: Allow single param to be used in the as function by @daslyfe in https://github.com/tidalcycles/strudel/pull/1312
|
||||
- fix: replace empty spaces in registered sound keys by @daslyfe in https://github.com/tidalcycles/strudel/pull/1319
|
||||
- FIX: Multichannel Audio by @daslyfe in https://github.com/tidalcycles/strudel/pull/1322
|
||||
- fix: udels header by @daslyfe in https://github.com/tidalcycles/strudel/pull/1325
|
||||
- fix: disable astro toolbar by default by @daslyfe in https://github.com/tidalcycles/strudel/pull/1324
|
||||
- FIX: sound import order by @daslyfe in https://github.com/tidalcycles/strudel/pull/1333`
|
||||
|
||||
## New Contributors
|
||||
|
||||
- @EdwardBetts made their first contribution in https://github.com/tidalcycles/strudel/pull/1183
|
||||
- @netux made their first contribution in https://github.com/tidalcycles/strudel/pull/1165
|
||||
- @kdiab made their first contribution in https://github.com/tidalcycles/strudel/pull/1189
|
||||
|
||||
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v1.1.0...v1.1.1
|
||||
|
||||
## packages
|
||||
|
||||
- @strudel/codemirror@1.2.0
|
||||
- @strudel/core@1.2.0
|
||||
- @strudel/csound@1.2.0
|
||||
- @strudel/draw@1.2.0
|
||||
- @strudel/gamepad@1.2.0
|
||||
- @strudel/hydra@1.2.0
|
||||
- @strudel/midi@1.2.0
|
||||
- @strudel/mini@1.2.0
|
||||
- @strudel/motion@1.2.0
|
||||
- @strudel/mqtt@1.2.0
|
||||
- @strudel/osc@1.2.0
|
||||
- @strudel/reference@1.2.0
|
||||
- @strudel/repl@1.2.0
|
||||
- @strudel/sampler@0.2.0
|
||||
- @strudel/serial@1.2.0
|
||||
- @strudel/soundfonts@1.2.0
|
||||
- superdough@1.2.0
|
||||
- @strudel/tonal@1.2.0
|
||||
- @strudel/transpiler@1.2.0
|
||||
- @strudel/web@1.2.0
|
||||
- @strudel/webaudio@1.2.0
|
||||
- @strudel/xen@1.2.0
|
||||
@@ -95,7 +95,7 @@ Diese Kombinationen von Buchstaben stehen für verschiedene Teile eines Schlagze
|
||||
- `mt` = **m**iddle tom
|
||||
- `ht` = **h**igh tom
|
||||
- `rd` = **r**i**d**e cymbal
|
||||
- `rd` = **cr**ash cymbal
|
||||
- `cr` = **cr**ash cymbal
|
||||
|
||||
Probier verschiedene Sounds aus!
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
## arp
|
||||
|
||||
<JsDoc client:idle name="Pattern#arp" h={0} />
|
||||
<JsDoc client:idle name="arp" h={0} />
|
||||
|
||||
## arpWith 🧪
|
||||
|
||||
<JsDoc client:idle name="Pattern#arpWith" h={0} />
|
||||
<JsDoc client:idle name="arpWith" h={0} />
|
||||
|
||||
## struct
|
||||
|
||||
@@ -58,7 +58,7 @@ import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
## hush
|
||||
|
||||
<JsDoc client:idle name="hush" h={0} />
|
||||
<JsDoc client:idle name="Pattern#hush" h={0} />
|
||||
|
||||
## invert
|
||||
|
||||
|
||||
@@ -11,6 +11,129 @@ import { JsDoc } from '../../docs/JsDoc';
|
||||
Whether you're using a synth or a sample, you can apply any of the following built-in audio effects.
|
||||
As you might suspect, the effects can be chained together, and they accept a pattern string as their argument.
|
||||
|
||||
# Signal chain
|
||||
|
||||
<img src="/img/strudel-signal-flow.png"></img>
|
||||
|
||||
The signal chain in Strudel is as follows:
|
||||
|
||||
- An sound-generating event is triggered by a pattern
|
||||
- This has a start time and a duration, which is usually
|
||||
controlled by the note length and ADSR parameters
|
||||
- If we exceed the max polyphony, old sounds begin to die off
|
||||
- Muted sounds (one whose `s` value is `-`, `~`, or `_`) are skipped
|
||||
- A sound is produced (through, say, a sample or an oscillator)
|
||||
- This is where detune-based effects (like `detune`, `penv`, etc. occur)
|
||||
- The following will occur _in order_ and only if they've been called in the pattern. Note that all of these are
|
||||
single use effects, meaning that multiple occurrences of them in a pattern will simply override the values
|
||||
(e.g. you can't do `s("bd").lpf(100).distort(2).lpf(800)` to lowpass, distort, and then lowpass
|
||||
again)
|
||||
- Phase vocoder (`stretch`)
|
||||
- Gain is applied (`gain`)
|
||||
- This is where the main (volume) ADSR happens
|
||||
- A lowpass filter (`lpf`)
|
||||
- A highpass filter (`hpf`)
|
||||
- A bandpass filter (`bandpass`)
|
||||
- A vowel filter (`vowel`)
|
||||
- Sample rate reduction (`coarse`)
|
||||
- Bit crushing (`crush`)
|
||||
- Waveshape distortion (`shape`)
|
||||
- Normal distortion (`distort`)
|
||||
- Tremolo (`tremolo`)
|
||||
- Compressor (`compressor`)
|
||||
- Panning (`pan`)
|
||||
- Phaser (`phaser`)
|
||||
- Postgain (`post`)
|
||||
- The sound is then split into multiple destinations
|
||||
- Dry output (amount controlled by `dry` parameter)
|
||||
- The sends
|
||||
- Analyzers
|
||||
- These are used for tooling like `scope` and `spectrum` and their setup usually happens behind the scenes
|
||||
- Delay (amount controlled by `delay` parameter)
|
||||
- Reverb (amount controlled by `room` parameter)
|
||||
- The dry output, delay, and reverb are joined into what is called the "orbit" of the pattern (see more in the section below)
|
||||
- The `duck` effect affects the volume of all signals in the orbit
|
||||
- The orbit is then sent to the mixer
|
||||
|
||||
## Orbits
|
||||
|
||||
Orbits are the way in which outputs are handled in Strudel. They also prescribe which delay and reverb to associate with the dry signal.
|
||||
By default, all orbits are mixed down to channels `1` and `2` in stereo, however with the "Multi Channel Orbits" setting
|
||||
(under Settings at the right) you can use them as individual 2 channel stereo outs (orbit `i` will be mapped to
|
||||
to channels `2i` and `2i + 1`). You can then use routers like Blackhole 16 to retrieve and record all of the channels in a DAW for later processing.
|
||||
|
||||
The default orbit is `1` and it is set with `orbit`. You may send a sound to multiple orbits via mininotation
|
||||
|
||||
<MiniRepl client:visible tune={`s("white").orbit("2,3,4").gain(0.2)`} />
|
||||
|
||||
but please be careful as this will create three copies of the sound behind the scenes, meaning that if they are mixed
|
||||
down to a single output, they will triple the volume. We've reduced the gain here to save your ears.
|
||||
|
||||
⚠️ There is only one delay and reverb per orbit, so please be aware that if you attempt to change the parameters on two
|
||||
patterns pointing to the same orbit, it can lead to unpredictable results. Compare, for example, this pretty pluck
|
||||
with a large reverb:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`
|
||||
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
|
||||
.room(1).roomsize(10)`}
|
||||
/>
|
||||
|
||||
versus the same pluck with a muted kick drum coming in and overwriting the `roomsize` value:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`
|
||||
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
|
||||
.room(1).roomsize(10)
|
||||
|
||||
$: s("bd\*4").room(0.01).roomsize(0.01).postgain(0)`}
|
||||
/>
|
||||
|
||||
This is due to them sharing the same orbit: the default of `1`. It can be corrected simply by updating the orbits to be
|
||||
distinct:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`
|
||||
$: s("triangle*4").decay(0.5).n(irand(12)).scale('C minor')
|
||||
.room(1).roomsize(10).orbit(2)
|
||||
|
||||
$: s("bd\*4").room(0.01).roomsize(0.01).postgain(0)`}
|
||||
/>
|
||||
|
||||
## Continuous changes
|
||||
|
||||
As all of the above is triggered by a _sound occurring_, it is often the case that parameters may not be
|
||||
modified continuously in time. For example,
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`
|
||||
s("supersaw").lpf(tri.range(100, 5000).slow(2))`}
|
||||
/>
|
||||
|
||||
Will not produce a continually LFO'd low-pass filter due to the `tri` only being sampled every time the note hits
|
||||
(in this case the default of once per cycle). You can fake it by introducing more sound-generating events, e.g.:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`
|
||||
s("supersaw").seg(16).lpf(tri.range(100, 5000).slow(2))`}
|
||||
/>
|
||||
|
||||
Some parameters _do_ induce continuous variations in time, though:
|
||||
|
||||
- The ADSR curve (governed by `attack`, `sustain`, `decay`, `release`)
|
||||
- The pitch envelope curve (governed by `penv` and its associated ADSR)
|
||||
- The FM curve (`fmenv`)
|
||||
- The filter envelopes (`lpenv`, `hpenv`, `bpenv`)
|
||||
- Tremolo (`tremolo`)
|
||||
- Phaser (`phaser`)
|
||||
- Vibrato (`vib`)
|
||||
- Ducking (`duckorbit`)
|
||||
|
||||
# Filters
|
||||
|
||||
Filters are an essential building block of [subtractive synthesis](https://en.wikipedia.org/wiki/Subtractive_synthesis).
|
||||
@@ -57,6 +180,34 @@ Each filter has 2 parameters:
|
||||
|
||||
<JsDoc client:idle name="vowel" h={0} />
|
||||
|
||||
# Amplitude Modulation
|
||||
|
||||
Amplitude modulation changes the amplitude (gain) periodically over time.
|
||||
|
||||
## am
|
||||
|
||||
<JsDoc client:idle name="am" h={0} />
|
||||
|
||||
## tremolosync
|
||||
|
||||
<JsDoc client:idle name="tremolosync" h={0} />
|
||||
|
||||
## tremolodepth
|
||||
|
||||
<JsDoc client:idle name="tremolodepth" h={0} />
|
||||
|
||||
## tremoloskew
|
||||
|
||||
<JsDoc client:idle name="tremoloskew" h={0} />
|
||||
|
||||
## tremolophase
|
||||
|
||||
<JsDoc client:idle name="tremolophase" h={0} />
|
||||
|
||||
## tremoloshape
|
||||
|
||||
<JsDoc client:idle name="tremoloshape" h={0} />
|
||||
|
||||
# Amplitude Envelope
|
||||
|
||||
The amplitude [envelope](<https://en.wikipedia.org/wiki/Envelope_(music)>) controls the dynamic contour of a sound.
|
||||
@@ -311,4 +462,18 @@ global effects use the same chain for all events of the same orbit:
|
||||
|
||||
<JsDoc client:idle name="phasersweep" h={0} />
|
||||
|
||||
## Duck
|
||||
|
||||
### duckorbit
|
||||
|
||||
<JsDoc client:idle name="duckorbit" h={0} />
|
||||
|
||||
### duckattack
|
||||
|
||||
<JsDoc client:idle name="duckattack" h={0} />
|
||||
|
||||
### duckdepth
|
||||
|
||||
<JsDoc client:idle name="duckdepth" h={0} />
|
||||
|
||||
Next, we'll look at input / output via [MIDI, OSC and other methods](/learn/input-output).
|
||||
|
||||
@@ -168,6 +168,20 @@ Using "!" we can repeat without speeding up:
|
||||
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>*2")`} punchcard />
|
||||
|
||||
## Randomness
|
||||
|
||||
Events with a "?" placed after them will have a 50% chance of being removed from the pattern:
|
||||
|
||||
<MiniRepl client:idle tune={`note("[g3,b3,e4]*8?")`} punchcard />
|
||||
|
||||
Adding a number between 0 and 1 after the "?" will affect the likelihood of the event being removed. For example, events with "?0.1" placed after them will have a 10% chance of being removed:
|
||||
|
||||
<MiniRepl client:idle tune={`note("[g3,b3,e4]*8?0.1")`} punchcard />
|
||||
|
||||
Events separated by a "|" will be chosen from at random:
|
||||
|
||||
<MiniRepl client:idle tune={`note("[g3,b3,e4] | [a3,c3,e4] | [b3,d3,f#4]")`} punchcard />
|
||||
|
||||
## Mini-notation review
|
||||
|
||||
To recap what we've learned so far, compare the following patterns:
|
||||
@@ -179,6 +193,8 @@ To recap what we've learned so far, compare the following patterns:
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] _ [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]? [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
<MiniRepl client:idle tune={`note("<[g3|b3|e4] [a3,c3,e4] [b3,d3,f#4]>*2")`} />
|
||||
|
||||
## Euclidian rhythms
|
||||
|
||||
|
||||
@@ -176,3 +176,16 @@ $ chord <Dm9!3 Db7> # voicing
|
||||
/>
|
||||
|
||||
The `$` sign is an alias for `,` so it will create a stack behind the scenes.
|
||||
|
||||
## variables
|
||||
|
||||
using the `def` keyword, you can define variables:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
mondo
|
||||
tune={`
|
||||
$ def melody [0 1 2 3]
|
||||
$ n melody # scale C:minor
|
||||
`}
|
||||
/>
|
||||
|
||||
@@ -163,6 +163,31 @@ The last section could be written as:
|
||||
}
|
||||
```
|
||||
|
||||
Please note that browsers will often cache `strudel.json` on first load, and keep using the cached
|
||||
version even if the orginal has been updated. If this bites you (for example while developing a new
|
||||
sample pack), you can force the browser to download a new copy by i.e. changing capitalization of one
|
||||
character in the URL, or adding a URL attribute, such as:
|
||||
|
||||
```javascript
|
||||
samples('https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/strudel.json?version=2');
|
||||
```
|
||||
|
||||
that gets ignored by GitHub (but changes the URL, forcing the browser to reload every time we increase
|
||||
the version number).
|
||||
|
||||
It is also possible, of course, to just remove it from cache (deleting cache in browser Privacy settings,
|
||||
or from the dev console if you're technically minded, or by using a cache deleting extension).
|
||||
|
||||
## Generating strudel.json
|
||||
|
||||
You can use [@strudel/sampler](https://www.npmjs.com/package/@strudel/sampler) to generate a strudel.json file for you, by running:
|
||||
|
||||
```sh
|
||||
npx --yes @strudel/sampler --json > strudel.json
|
||||
```
|
||||
|
||||
See other uses of strudel/sampler further below, under "From Disk via @strudel/sampler".
|
||||
|
||||
## Github Shortcut
|
||||
|
||||
Because loading samples from github is common, there is a shortcut:
|
||||
@@ -346,6 +371,10 @@ Sampler effects are functions that can be used to change the behaviour of sample
|
||||
|
||||
<JsDoc client:idle name="splice" h={0} />
|
||||
|
||||
### scrub
|
||||
|
||||
<JsDoc client:idle name="Pattern.scrub" h={0} />{' '}
|
||||
|
||||
### speed
|
||||
|
||||
<JsDoc client:idle name="speed" h={0} />
|
||||
|
||||
@@ -9,7 +9,9 @@ import { MiniRepl } from '../../docs/MiniRepl';
|
||||
|
||||
{/* The [REPL](https://strudel.cc/) 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. */}
|
||||
|
||||
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, highlighting 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.
|
||||
While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the Strudel REPL[^1], 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, highlighting 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.
|
||||
|
||||
[^1]: 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.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ Or using 2 beats per cycle:
|
||||
s("bd sd, hh*4")`}
|
||||
/>
|
||||
|
||||
You can use the `setcps` method to set the global tempo in cycles per second. `setcpm(x)` is the same as `setcps(x / 60)`.
|
||||
|
||||
<Box>
|
||||
|
||||
To set a specific bpm, use `setcpm(bpm/bpc)`
|
||||
|
||||
@@ -69,3 +69,141 @@
|
||||
text-decoration: underline 0.18rem;
|
||||
text-underline-offset: 0.22rem;
|
||||
}
|
||||
|
||||
/* Override default styles from the codemirror inline css for autocomplete info tooltip*/
|
||||
.cm-tooltip.cm-completionInfo {
|
||||
padding: 0 !important;
|
||||
border: 1px solid var(--foreground) !important;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
|
||||
max-width: 500px !important;
|
||||
min-width: 300px !important;
|
||||
max-height: 400px !important;
|
||||
background-color: var(--lineHighlight) !important;
|
||||
}
|
||||
|
||||
/* Main tooltip container */
|
||||
.autocomplete-info-container {
|
||||
padding: 12px !important;
|
||||
border-radius: 4px !important;
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-family, 'SF Mono', 'Monaco', monospace);
|
||||
font-size: var(--font-size, 13px);
|
||||
line-height: 1.4;
|
||||
max-width: 600px;
|
||||
max-height: 400px;
|
||||
min-width: 400px;
|
||||
white-space: normal !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.autocomplete-info-tooltip {
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.autocomplete-info-function-description {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
.autocomplete-info-function-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--foreground);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.autocomplete-info-function-description {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--foreground);
|
||||
line-height: 1.5;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.autocomplete-info-section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--foreground);
|
||||
margin: 16px 0 6px 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.autocomplete-info-section-title:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.autocomplete-info-params-section {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.autocomplete-info-params-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.autocomplete-info-param-item {
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
background-color: var(--lineBackground);
|
||||
border-radius: 3px;
|
||||
border-left: 2px solid var(--foreground, #555);
|
||||
}
|
||||
|
||||
.autocomplete-info-param-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.autocomplete-info-param-name {
|
||||
font-weight: 600;
|
||||
color: var(--variable, var(--foreground));
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.autocomplete-info-param-type {
|
||||
color: var(--comment);
|
||||
font-size: 12px;
|
||||
background-color: var(--gutterForeground);
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.autocomplete-info-param-desc {
|
||||
color: var(--foreground);
|
||||
font-size: 10px;
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.autocomplete-info-examples-section {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.autocomplete-info-example-code {
|
||||
background: var(--lineBackground);
|
||||
color: var(--foreground);
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-family, 'SF Mono', 'Monaco', monospace);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
margin: 4px 0;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
border: 1px solid var(--foreground, #3a3a3a);
|
||||
}
|
||||
|
||||
.autocomplete-info-tooltip::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.autocomplete-info-tooltip::-webkit-scrollbar-track {
|
||||
}
|
||||
|
||||
.autocomplete-info-tooltip::-webkit-scrollbar-thumb {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.autocomplete-info-tooltip::-webkit-scrollbar-thumb:hover {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import cx from '@src/cx.mjs';
|
||||
|
||||
export function ActionButton({ children, label, labelIsHidden, className, ...buttonProps }) {
|
||||
return (
|
||||
<button className={cx('hover:opacity-50 text-nowrap w-fit', className)} title={label} {...buttonProps}>
|
||||
{labelIsHidden !== true && label}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import { useMemo } from 'react';
|
||||
import { getMetadata } from '../../../metadata_parser.js';
|
||||
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
||||
import { parseJSON, isUdels } from '../../util.mjs';
|
||||
import { ButtonGroup } from './Forms.jsx';
|
||||
import { settingsMap, useSettings } from '../../../settings.mjs';
|
||||
import { useSettings } from '../../../settings.mjs';
|
||||
import { ActionButton } from '../button/action-button.jsx';
|
||||
import { Pagination } from '../pagination/Pagination.jsx';
|
||||
import { useState } from 'react';
|
||||
import { useDebounce } from '../usedebounce.jsx';
|
||||
@@ -75,15 +75,6 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ActionButton({ children, onClick, label, labelIsHidden }) {
|
||||
return (
|
||||
<button className="hover:opacity-50 text-nowrap" onClick={onClick} title={label}>
|
||||
{labelIsHidden !== true && label}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const updateCodeWindow = (context, patternData, reset = false) => {
|
||||
context.handleUpdate(patternData, reset);
|
||||
};
|
||||
@@ -125,7 +116,7 @@ function UserPatterns({ context }) {
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
multiple
|
||||
accept="text/plain,application/json"
|
||||
accept="text/plain,text/x-markdown,application/json"
|
||||
onChange={(e) => importPatterns(e.target.files)}
|
||||
/>
|
||||
import
|
||||
|
||||
@@ -109,6 +109,8 @@ export function SettingsTab({ started }) {
|
||||
togglePanelTrigger,
|
||||
maxPolyphony,
|
||||
multiChannelOrbits,
|
||||
isTabIndentationEnabled,
|
||||
isMultiCursorEnabled,
|
||||
} = useSettings();
|
||||
const shouldAlwaysSync = isUdels();
|
||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||
@@ -262,6 +264,16 @@ export function SettingsTab({ started }) {
|
||||
onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)}
|
||||
value={isLineWrappingEnabled}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable Tab indentation"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isTabIndentationEnabled', cbEvent.target.checked)}
|
||||
value={isTabIndentationEnabled}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable Multi-Cursor (Cmd/Ctrl+Click)"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isMultiCursorEnabled', cbEvent.target.checked)}
|
||||
value={isMultiCursorEnabled}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable flashing on evaluation"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)}
|
||||
@@ -299,7 +311,8 @@ export function SettingsTab({ started }) {
|
||||
onClick={() => {
|
||||
confirmDialog('Sure?').then((r) => {
|
||||
if (r) {
|
||||
settingsMap.set(defaultSettings);
|
||||
const { userPatterns } = settingsMap.get(); // keep current patterns
|
||||
settingsMap.set({ ...defaultSettings, userPatterns });
|
||||
}
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -2,16 +2,21 @@ import useEvent from '@src/useEvent.mjs';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { getAudioContext, soundMap, connectToDestination } from '@strudel/webaudio';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { settingsMap, useSettings } from '../../../settings.mjs';
|
||||
import { settingsMap, soundFilterType, useSettings } from '../../../settings.mjs';
|
||||
import { ButtonGroup } from './Forms.jsx';
|
||||
import ImportSoundsButton from './ImportSoundsButton.jsx';
|
||||
import { Textbox } from '../textbox/Textbox.jsx';
|
||||
import { ActionButton } from '../button/action-button.jsx';
|
||||
import { confirmDialog } from '@src/repl/util.mjs';
|
||||
import { clearIDB, userSamplesDBConfig } from '@src/repl/idbutils.mjs';
|
||||
import { prebake } from '@src/repl/prebake.mjs';
|
||||
|
||||
const getSamples = (samples) =>
|
||||
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
|
||||
|
||||
export function SoundsTab() {
|
||||
const sounds = useStore(soundMap);
|
||||
|
||||
const { soundsFilter } = useSettings();
|
||||
const [search, setSearch] = useState('');
|
||||
const { BASE_URL } = import.meta.env;
|
||||
@@ -27,18 +32,19 @@ export function SoundsTab() {
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.filter(([name]) => name.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
if (soundsFilter === 'user') {
|
||||
if (soundsFilter === soundFilterType.USER) {
|
||||
return filtered.filter(([_, { data }]) => !data.prebake);
|
||||
}
|
||||
if (soundsFilter === 'drums') {
|
||||
if (soundsFilter === soundFilterType.DRUMS) {
|
||||
return filtered.filter(([_, { data }]) => data.type === 'sample' && data.tag === 'drum-machines');
|
||||
}
|
||||
if (soundsFilter === 'samples') {
|
||||
if (soundsFilter === soundFilterType.SAMPLES) {
|
||||
return filtered.filter(([_, { data }]) => data.type === 'sample' && data.tag !== 'drum-machines');
|
||||
}
|
||||
if (soundsFilter === 'synths') {
|
||||
if (soundsFilter === soundFilterType.SYNTHS) {
|
||||
return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type));
|
||||
}
|
||||
//TODO: tidy this up, it does not need to be saved in settings
|
||||
if (soundsFilter === 'importSounds') {
|
||||
return [];
|
||||
}
|
||||
@@ -57,10 +63,10 @@ export function SoundsTab() {
|
||||
});
|
||||
});
|
||||
return (
|
||||
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full text-foreground">
|
||||
<div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground">
|
||||
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
|
||||
|
||||
<div className="pb-2 flex shrink-0 flex-wrap">
|
||||
<div className=" flex shrink-0 flex-wrap">
|
||||
<ButtonGroup
|
||||
value={soundsFilter}
|
||||
onChange={(value) => settingsMap.setKey('soundsFilter', value)}
|
||||
@@ -74,7 +80,26 @@ export function SoundsTab() {
|
||||
></ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 max-h-full grow overflow-auto text-sm break-normal pb-2">
|
||||
{soundsFilter === soundFilterType.USER && soundEntries.length > 0 && (
|
||||
<ActionButton
|
||||
className="pl-2"
|
||||
label="delete-all"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const confirmed = await confirmDialog('Delete all imported user samples?');
|
||||
if (confirmed) {
|
||||
clearIDB(userSamplesDBConfig.dbName);
|
||||
soundMap.set({});
|
||||
await prebake();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="min-h-0 max-h-full grow overflow-auto text-sm break-normal bg-background p-2 rounded-md">
|
||||
{soundEntries.map(([name, { data, onTrigger }]) => {
|
||||
return (
|
||||
<span
|
||||
@@ -151,9 +176,7 @@ export function SoundsTab() {
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{!soundEntries.length && soundsFilter !== 'importSounds'
|
||||
? 'No custom sounds loaded in this pattern (yet).'
|
||||
: ''}
|
||||
{!soundEntries.length && soundsFilter !== 'importSounds' ? 'No sounds loaded' : ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,17 +12,21 @@ export const userSamplesDBConfig = {
|
||||
};
|
||||
|
||||
// deletes all of the databases, useful for debugging
|
||||
function clearIDB() {
|
||||
function clearAllIDB() {
|
||||
window.indexedDB
|
||||
.databases()
|
||||
.then((r) => {
|
||||
for (var i = 0; i < r.length; i++) window.indexedDB.deleteDatabase(r[i].name);
|
||||
for (var i = 0; i < r.length; i++) clearIDB(r[i].name);
|
||||
})
|
||||
.then(() => {
|
||||
alert('All data cleared.');
|
||||
});
|
||||
}
|
||||
|
||||
export function clearIDB(dbName) {
|
||||
return window.indexedDB.deleteDatabase(dbName);
|
||||
}
|
||||
|
||||
// queries the DB, and registers the sounds so they can be played
|
||||
export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete = () => {}) {
|
||||
openDB(config, (objectStore) => {
|
||||
|
||||
@@ -28,7 +28,10 @@ export async function prebake() {
|
||||
prebake: true,
|
||||
tag: 'drum-machines',
|
||||
}),
|
||||
samples(`${baseNoTrailing}/EmuSP12.json`, undefined, { prebake: true, tag: 'drum-machines' }),
|
||||
samples(`${baseNoTrailing}/uzu-drumkit.json`, undefined, {
|
||||
prebake: true,
|
||||
tag: 'drum-machines',
|
||||
}),
|
||||
samples(`${baseNoTrailing}/mridangam.json`, undefined, { prebake: true, tag: 'drum-machines' }),
|
||||
samples(
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
resetGlobalEffects,
|
||||
resetLoadedSounds,
|
||||
initAudioOnFirstClick,
|
||||
resetDefaults,
|
||||
} from '@strudel/webaudio';
|
||||
import { setVersionDefaultsFrom } from './util.mjs';
|
||||
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
||||
@@ -181,6 +182,7 @@ export function useReplContext() {
|
||||
|
||||
const resetEditor = async () => {
|
||||
(await getModule('@strudel/tonal'))?.resetVoicings();
|
||||
resetDefaults();
|
||||
resetGlobalEffects();
|
||||
clearCanvas();
|
||||
clearHydra();
|
||||
|
||||
@@ -8,6 +8,14 @@ export const audioEngineTargets = {
|
||||
osc: 'osc',
|
||||
};
|
||||
|
||||
export const soundFilterType = {
|
||||
USER: 'user',
|
||||
DRUMS: 'drums',
|
||||
SAMPLES: 'samples',
|
||||
SYNTHS: 'synths',
|
||||
ALL: 'all',
|
||||
};
|
||||
|
||||
export const defaultSettings = {
|
||||
activeFooter: 'intro',
|
||||
keybindings: 'codemirror',
|
||||
@@ -21,12 +29,14 @@ export const defaultSettings = {
|
||||
isSyncEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
isPatternHighlightingEnabled: true,
|
||||
isTabIndentationEnabled: false,
|
||||
isMultiCursorEnabled: false,
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
latestCode: '',
|
||||
isZen: false,
|
||||
soundsFilter: 'all',
|
||||
soundsFilter: soundFilterType.ALL,
|
||||
patternFilter: 'community',
|
||||
// panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
|
||||
panelPosition: 'right',
|
||||
@@ -77,6 +87,8 @@ export function useSettings() {
|
||||
isLineWrappingEnabled: parseBoolean(state.isLineWrappingEnabled),
|
||||
isFlashEnabled: parseBoolean(state.isFlashEnabled),
|
||||
isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled),
|
||||
isTabIndentationEnabled: parseBoolean(state.isTabIndentationEnabled),
|
||||
isMultiCursorEnabled: parseBoolean(state.isMultiCursorEnabled),
|
||||
fontSize: Number(state.fontSize),
|
||||
panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
|
||||
isPanelPinned: parseBoolean(state.isPanelPinned),
|
||||
|
||||
@@ -197,7 +197,7 @@ export async function importPatterns(fileList) {
|
||||
if (file.type === 'application/json') {
|
||||
const userPatterns = userPattern.getAll();
|
||||
setUserPatterns({ ...userPatterns, ...parseJSON(content) });
|
||||
} else if (file.type === 'text/plain') {
|
||||
} else if (['text/x-markdown', 'text/plain'].includes(file.type)) {
|
||||
const id = file.name.replace(/\.[^/.]+$/, '');
|
||||
userPattern.update(id, { code: content });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user