mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-27 21:48:27 +00:00
Merge branch 'main' into patterns-tab
This commit is contained in:
commit
e72c9cbebf
@ -20,4 +20,5 @@ vite.config.js
|
|||||||
**/dist
|
**/dist
|
||||||
/src-tauri/target/**/*
|
/src-tauri/target/**/*
|
||||||
reverbGen.mjs
|
reverbGen.mjs
|
||||||
hydra.mjs
|
hydra.mjs
|
||||||
|
jsdoc-synonyms.js
|
||||||
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@ -21,10 +21,10 @@ jobs:
|
|||||||
name: github-pages
|
name: github-pages
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 7
|
version: 8.11.0
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
|
|||||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -10,10 +10,10 @@ jobs:
|
|||||||
node-version: [18]
|
node-version: [18]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 7
|
version: 8.11.0
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|||||||
85
.gitignore
vendored
85
.gitignore
vendored
@ -43,4 +43,87 @@ dev-dist
|
|||||||
Dirt-Samples
|
Dirt-Samples
|
||||||
tidal-drum-machines
|
tidal-drum-machines
|
||||||
webaudiofontdata
|
webaudiofontdata
|
||||||
src-tauri/target
|
src-tauri/target
|
||||||
|
|
||||||
|
# BEGIN JetBrains -> END JetBrains
|
||||||
|
|
||||||
|
# for JetBrains IDE users, e.g. WebStorm. Source: https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
# END JetBrains -> BEGIN JetBrains
|
||||||
|
|||||||
@ -66,7 +66,7 @@ To get the project up and running for development, make sure you have installed:
|
|||||||
|
|
||||||
- [git](https://git-scm.com/)
|
- [git](https://git-scm.com/)
|
||||||
- [node](https://nodejs.org/en/) >= 18
|
- [node](https://nodejs.org/en/) >= 18
|
||||||
- [pnpm](https://pnpm.io/) (`npm i pnpm -g`)
|
- [pnpm](https://pnpm.io/) (`curl -fsSL https://get.pnpm.io/install.sh | env PNPM_VERSION=8.11.0 sh -`)
|
||||||
|
|
||||||
then, do the following:
|
then, do the following:
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,6 @@ There are multiple npm packages you can use to use strudel, or only parts of it,
|
|||||||
- [`midi`](./packages/midi): webmidi bindings
|
- [`midi`](./packages/midi): webmidi bindings
|
||||||
- [`serial`](./packages/serial): webserial bindings
|
- [`serial`](./packages/serial): webserial bindings
|
||||||
- [`tonal`](./packages/tonal): tonal functions
|
- [`tonal`](./packages/tonal): tonal functions
|
||||||
- [`xen`](./packages/xen): microtonal / xenharmonic functions
|
|
||||||
- ... [and there are more](./packages/)
|
- ... [and there are more](./packages/)
|
||||||
|
|
||||||
Click on the package names to find out more about each one.
|
Click on the package names to find out more about each one.
|
||||||
|
|||||||
17
jsdoc/jsdoc-synonyms.js
Normal file
17
jsdoc/jsdoc-synonyms.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
jsdoc-synonyms.js - Add support for @synonym tag
|
||||||
|
Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/midi/midi.mjs>
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function defineTags(dictionary) {
|
||||||
|
dictionary.defineTag('synonyms', {
|
||||||
|
mustHaveValue: true,
|
||||||
|
onTagged: function (doclet, tag) {
|
||||||
|
doclet.synonyms_text = tag.value;
|
||||||
|
doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { defineTags: defineTags };
|
||||||
@ -3,7 +3,7 @@
|
|||||||
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
||||||
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
|
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
|
||||||
},
|
},
|
||||||
"plugins": ["plugins/markdown"],
|
"plugins": ["plugins/markdown", "jsdoc/jsdoc-synonyms"],
|
||||||
"opts": {
|
"opts": {
|
||||||
"destination": "./out/",
|
"destination": "./out/",
|
||||||
"recurse": true
|
"recurse": true
|
||||||
@ -65,7 +65,7 @@ async function getUndocumented(path, docs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// read doc.json file
|
// read doc.json file
|
||||||
const { docs } = JSON.parse(await readFile(resolve(__dirname, 'doc.json'), 'utf8'));
|
const { docs } = JSON.parse(await readFile(resolve(__dirname, '..', 'doc.json'), 'utf8'));
|
||||||
|
|
||||||
const paths = dependencyTree.toList({
|
const paths = dependencyTree.toList({
|
||||||
filename: 'index.mjs',
|
filename: 'index.mjs',
|
||||||
@ -76,7 +76,9 @@ const paths = dependencyTree.toList({
|
|||||||
// const paths = ['../packages/core/pattern.mjs', '../packages/core/hap.mjs'].map((rel) => resolve(__dirname, rel));
|
// const paths = ['../packages/core/pattern.mjs', '../packages/core/hap.mjs'].map((rel) => resolve(__dirname, rel));
|
||||||
|
|
||||||
const undocumented = Object.fromEntries(
|
const undocumented = Object.fromEntries(
|
||||||
await Promise.all(paths.map(async (path) => [path, await getUndocumented(path, docs)])),
|
await Promise.all(
|
||||||
|
paths.map(async (path) => [path.replace(resolve(__dirname, '..'), ''), await getUndocumented(path, docs)]),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(JSON.stringify(undocumented, null, 2));
|
console.log(JSON.stringify(undocumented, null, 2));
|
||||||
@ -18,12 +18,12 @@
|
|||||||
"build": "npm run prebuild && cd website && npm run build",
|
"build": "npm run prebuild && cd website && npm run build",
|
||||||
"preview": "cd website && npm run preview",
|
"preview": "cd website && npm run preview",
|
||||||
"osc": "cd packages/osc && npm run server",
|
"osc": "cd packages/osc && npm run server",
|
||||||
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
|
"jsdoc": "jsdoc packages/ -c jsdoc/jsdoc.config.json",
|
||||||
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
|
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc/jsdoc.config.json",
|
||||||
"lint": "eslint . --ext mjs,js --quiet",
|
"lint": "eslint . --ext mjs,js --quiet",
|
||||||
"codeformat": "prettier --write .",
|
"codeformat": "prettier --write .",
|
||||||
"format-check": "prettier --check .",
|
"format-check": "prettier --check .",
|
||||||
"report-undocumented": "npm run jsdoc-json && node undocumented.mjs > undocumented.json",
|
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
|
||||||
"check": "npm run format-check && npm run lint && npm run test",
|
"check": "npm run format-check && npm run lint && npm run test",
|
||||||
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
|
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -381,6 +381,19 @@ const generic_params = [
|
|||||||
*/
|
*/
|
||||||
['coarse'],
|
['coarse'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to set the output channels on the interface
|
||||||
|
*
|
||||||
|
* @name channels
|
||||||
|
* @synonyms ch
|
||||||
|
*
|
||||||
|
* @param {number | Pattern} channels pattern the output channels
|
||||||
|
* @example
|
||||||
|
* note("e a d b g").channels("3:4")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['channels', 'ch'],
|
||||||
|
|
||||||
['phaserrate', 'phasr'], // superdirt only
|
['phaserrate', 'phasr'], // superdirt only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1214,6 +1227,16 @@ const generic_params = [
|
|||||||
* @name waveloss
|
* @name waveloss
|
||||||
*/
|
*/
|
||||||
['waveloss'],
|
['waveloss'],
|
||||||
|
/*
|
||||||
|
* Noise crackle density
|
||||||
|
*
|
||||||
|
* @name density
|
||||||
|
* @param {number | Pattern} density between 0 and x
|
||||||
|
* @example
|
||||||
|
* s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4))
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['density'],
|
||||||
// TODO: midi effects?
|
// TODO: midi effects?
|
||||||
['dur'],
|
['dur'],
|
||||||
// ['modwheel'],
|
// ['modwheel'],
|
||||||
|
|||||||
@ -1985,7 +1985,7 @@ Pattern.prototype.hush = function () {
|
|||||||
* note("c d e g").palindrome()
|
* note("c d e g").palindrome()
|
||||||
*/
|
*/
|
||||||
export const palindrome = register('palindrome', function (pat) {
|
export const palindrome = register('palindrome', function (pat) {
|
||||||
return pat.every(2, rev);
|
return pat.lastOf(2, rev);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2189,6 +2189,14 @@ export const duration = register('duration', function (value, pat) {
|
|||||||
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const hsla = register('hsla', (h, s, l, a, pat) => {
|
||||||
|
return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const hsl = register('hsl', (h, s, l, pat) => {
|
||||||
|
return pat.color(`hsl(${h}turn,${s * 100}%,${l * 100}%)`);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the color of the hap in visualizations like pianoroll or highlighting.
|
* Sets the color of the hap in visualizations like pianoroll or highlighting.
|
||||||
* @name color
|
* @name color
|
||||||
|
|||||||
@ -89,19 +89,22 @@ export function repl({
|
|||||||
allTransform = transform;
|
allTransform = transform;
|
||||||
return silence;
|
return silence;
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
for (let i = 1; i < 10; ++i) {
|
for (let i = 1; i < 10; ++i) {
|
||||||
Object.defineProperty(Pattern.prototype, `d${i}`, {
|
Object.defineProperty(Pattern.prototype, `d${i}`, {
|
||||||
get() {
|
get() {
|
||||||
return this.p(i);
|
return this.p(i);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Object.defineProperty(Pattern.prototype, `p${i}`, {
|
Object.defineProperty(Pattern.prototype, `p${i}`, {
|
||||||
get() {
|
get() {
|
||||||
return this.p(i);
|
return this.p(i);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Pattern.prototype[`q${i}`] = silence;
|
Pattern.prototype[`q${i}`] = silence;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// already defined..
|
||||||
}
|
}
|
||||||
|
|
||||||
const fit = register('fit', (pat) =>
|
const fit = register('fit', (pat) =>
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
slowcat,
|
slowcat,
|
||||||
cat,
|
cat,
|
||||||
sequence,
|
sequence,
|
||||||
|
palindrome,
|
||||||
polymeter,
|
polymeter,
|
||||||
polymeterSteps,
|
polymeterSteps,
|
||||||
polyrhythm,
|
polyrhythm,
|
||||||
@ -571,6 +572,18 @@ describe('Pattern', () => {
|
|||||||
expect(sequence(1, 2, 3).firstCycle()).toStrictEqual(fastcat(1, 2, 3).firstCycle());
|
expect(sequence(1, 2, 3).firstCycle()).toStrictEqual(fastcat(1, 2, 3).firstCycle());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('palindrome()', () => {
|
||||||
|
it('Can create palindrome', () => {
|
||||||
|
expect(
|
||||||
|
fastcat('a', 'b', 'c')
|
||||||
|
.palindrome()
|
||||||
|
.fast(2)
|
||||||
|
.firstCycle()
|
||||||
|
.sort((a, b) => a.part.begin.sub(b.part.begin))
|
||||||
|
.map((a) => a.value),
|
||||||
|
).toStrictEqual(['a', 'b', 'c', 'c', 'b', 'a']);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('polyrhythm()', () => {
|
describe('polyrhythm()', () => {
|
||||||
it('Can layer up cycles', () => {
|
it('Can layer up cycles', () => {
|
||||||
expect(polyrhythm(['a', 'b'], ['c']).firstCycle()).toStrictEqual(
|
expect(polyrhythm(['a', 'b'], ['c']).firstCycle()).toStrictEqual(
|
||||||
|
|||||||
@ -12,6 +12,12 @@ await initHydra();
|
|||||||
|
|
||||||
Then you can use hydra below!
|
Then you can use hydra below!
|
||||||
|
|
||||||
|
### options
|
||||||
|
|
||||||
|
You can also pass options to the `initHydra` function. These can be used to set [hydra options](https://github.com/hydra-synth/hydra-synth#api) + these strudel specific options:
|
||||||
|
|
||||||
|
- `feedStrudel`: sends the strudel canvas to `s0`. The strudel canvas is used to draw `pianoroll`, `spiral`, `scope` etc..
|
||||||
|
|
||||||
## Usage via npm
|
## Usage via npm
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@ -1,14 +1,38 @@
|
|||||||
import { getDrawContext } from '@strudel.cycles/core';
|
import { getDrawContext } from '@strudel.cycles/core';
|
||||||
|
|
||||||
export async function initHydra() {
|
let latestOptions;
|
||||||
|
|
||||||
|
function appendCanvas(c) {
|
||||||
|
const { canvas: testCanvas } = getDrawContext();
|
||||||
|
c.canvas.id = 'hydra-canvas';
|
||||||
|
c.canvas.style.position = 'fixed';
|
||||||
|
c.canvas.style.top = '0px';
|
||||||
|
testCanvas.after(c.canvas);
|
||||||
|
return testCanvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initHydra(options = {}) {
|
||||||
|
// reset if options have changed since last init
|
||||||
|
if (latestOptions && JSON.stringify(latestOptions) !== JSON.stringify(options)) {
|
||||||
|
document.getElementById('hydra-canvas').remove();
|
||||||
|
}
|
||||||
|
latestOptions = options;
|
||||||
|
//load and init hydra
|
||||||
if (!document.getElementById('hydra-canvas')) {
|
if (!document.getElementById('hydra-canvas')) {
|
||||||
const { canvas: testCanvas } = getDrawContext();
|
console.log('reinit..');
|
||||||
await import('https://unpkg.com/hydra-synth');
|
const {
|
||||||
const hydraCanvas = testCanvas.cloneNode(true);
|
src = 'https://unpkg.com/hydra-synth',
|
||||||
hydraCanvas.id = 'hydra-canvas';
|
feedStrudel = false,
|
||||||
testCanvas.after(hydraCanvas);
|
...hydraConfig
|
||||||
new Hydra({ canvas: hydraCanvas, detectAudio: false });
|
} = { detectAudio: false, ...options };
|
||||||
s0.init({ src: testCanvas });
|
await import(src);
|
||||||
|
const hydra = new Hydra(hydraConfig);
|
||||||
|
if (feedStrudel) {
|
||||||
|
const { canvas } = getDrawContext();
|
||||||
|
canvas.style.display = 'none';
|
||||||
|
hydra.synth.s0.init({ src: canvas });
|
||||||
|
}
|
||||||
|
appendCanvas(hydra);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,16 +2,23 @@ import { createRoot } from 'react-dom/client';
|
|||||||
import jsdoc from '../../../../doc.json';
|
import jsdoc from '../../../../doc.json';
|
||||||
|
|
||||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||||
|
const getDocSynonyms = (doc) => [getDocLabel(doc), ...(doc.synonyms || [])];
|
||||||
const getInnerText = (html) => {
|
const getInnerText = (html) => {
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
return div.textContent || div.innerText || '';
|
return div.textContent || div.innerText || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Autocomplete({ doc }) {
|
export function Autocomplete({ doc, label = getDocLabel(doc) }) {
|
||||||
|
const synonyms = getDocSynonyms(doc).filter((a) => a !== label);
|
||||||
return (
|
return (
|
||||||
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
|
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
|
||||||
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
|
<h3 className="pt-0 mt-0">{label}</h3>{' '}
|
||||||
|
{!!synonyms.length && (
|
||||||
|
<span>
|
||||||
|
Synonyms: <code>{synonyms.join(', ')}</code>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
|
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
|
||||||
<ul>
|
<ul>
|
||||||
{doc.params?.map(({ name, type, description }, i) => (
|
{doc.params?.map(({ name, type, description }, i) => (
|
||||||
@ -48,18 +55,24 @@ const jsdocCompletions = jsdoc.docs
|
|||||||
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
|
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
|
||||||
)
|
)
|
||||||
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||||
.map((doc) /*: Completion */ => ({
|
.reduce(
|
||||||
label: getDocLabel(doc),
|
(acc, doc) /*: Completion */ =>
|
||||||
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
acc.concat(
|
||||||
info: () => {
|
[getDocLabel(doc), ...(doc.synonyms || [])].map((label) => ({
|
||||||
const node = document.createElement('div');
|
label,
|
||||||
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
||||||
// .. using renderToStaticMarkup
|
info: () => {
|
||||||
createRoot(node).render(<Autocomplete doc={doc} />);
|
const node = document.createElement('div');
|
||||||
return node;
|
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
||||||
},
|
// .. using renderToStaticMarkup
|
||||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
createRoot(node).render(<Autocomplete doc={doc} label={label} />);
|
||||||
}));
|
return node;
|
||||||
|
},
|
||||||
|
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
||||||
let word = context.matchBefore(/\w*/);
|
let word = context.matchBefore(/\w*/);
|
||||||
|
|||||||
@ -31,6 +31,9 @@ window.addEventListener(
|
|||||||
export const strudelTooltip = hoverTooltip(
|
export const strudelTooltip = hoverTooltip(
|
||||||
(view, pos, side) => {
|
(view, pos, side) => {
|
||||||
// Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips
|
// Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips
|
||||||
|
if (!ctrlDown) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
let { from, to, text } = view.state.doc.lineAt(pos);
|
let { from, to, text } = view.state.doc.lineAt(pos);
|
||||||
let start = pos,
|
let start = pos,
|
||||||
end = pos;
|
end = pos;
|
||||||
@ -47,11 +50,13 @@ export const strudelTooltip = hoverTooltip(
|
|||||||
// Get entry from Strudel documentation
|
// Get entry from Strudel documentation
|
||||||
let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0];
|
let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0];
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return null;
|
// Try for synonyms
|
||||||
}
|
entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0];
|
||||||
if (!ctrlDown) {
|
if (!entry) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pos: start,
|
pos: start,
|
||||||
end,
|
end,
|
||||||
@ -60,7 +65,7 @@ export const strudelTooltip = hoverTooltip(
|
|||||||
create(view) {
|
create(view) {
|
||||||
let dom = document.createElement('div');
|
let dom = document.createElement('div');
|
||||||
dom.className = 'strudel-tooltip';
|
dom.className = 'strudel-tooltip';
|
||||||
createRoot(dom).render(<Autocomplete doc={entry} />);
|
createRoot(dom).render(<Autocomplete doc={entry} label={word} />);
|
||||||
return { dom };
|
return { dom };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { getAudioContext } from './superdough.mjs';
|
|||||||
let noiseCache = {};
|
let noiseCache = {};
|
||||||
|
|
||||||
// lazy generates noise buffers and keeps them forever
|
// lazy generates noise buffers and keeps them forever
|
||||||
function getNoiseBuffer(type) {
|
function getNoiseBuffer(type, density) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
if (noiseCache[type]) {
|
if (noiseCache[type]) {
|
||||||
return noiseCache[type];
|
return noiseCache[type];
|
||||||
@ -34,17 +34,26 @@ function getNoiseBuffer(type) {
|
|||||||
output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
|
output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
|
||||||
output[i] *= 0.11;
|
output[i] *= 0.11;
|
||||||
b6 = white * 0.115926;
|
b6 = white * 0.115926;
|
||||||
|
} else if (type === 'crackle') {
|
||||||
|
const probability = density * 0.01;
|
||||||
|
if (Math.random() < probability) {
|
||||||
|
output[i] = Math.random() * 2 - 1;
|
||||||
|
} else {
|
||||||
|
output[i] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
noiseCache[type] = noiseBuffer;
|
|
||||||
|
// Prevent caching to randomize crackles
|
||||||
|
if (type !== 'crackle') noiseCache[type] = noiseBuffer;
|
||||||
return noiseBuffer;
|
return noiseBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// expects one of noises as type
|
// expects one of noises as type
|
||||||
export function getNoiseOscillator(type = 'white', t) {
|
export function getNoiseOscillator(type = 'white', t, density = 0.02) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const o = ac.createBufferSource();
|
const o = ac.createBufferSource();
|
||||||
o.buffer = getNoiseBuffer(type);
|
o.buffer = getNoiseBuffer(type, density);
|
||||||
o.loop = true;
|
o.loop = true;
|
||||||
o.start(t);
|
o.start(t);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -27,28 +27,16 @@ export function getSound(s) {
|
|||||||
export const resetLoadedSounds = () => soundMap.set({});
|
export const resetLoadedSounds = () => soundMap.set({});
|
||||||
|
|
||||||
let audioContext;
|
let audioContext;
|
||||||
|
|
||||||
export const getAudioContext = () => {
|
export const getAudioContext = () => {
|
||||||
if (!audioContext) {
|
if (!audioContext) {
|
||||||
audioContext = new AudioContext();
|
audioContext = new AudioContext();
|
||||||
|
const maxChannelCount = audioContext.destination.maxChannelCount;
|
||||||
|
audioContext.destination.channelCount = maxChannelCount;
|
||||||
}
|
}
|
||||||
return audioContext;
|
return audioContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
let destination;
|
|
||||||
const getDestination = () => {
|
|
||||||
const ctx = getAudioContext();
|
|
||||||
if (!destination) {
|
|
||||||
destination = ctx.createGain();
|
|
||||||
destination.connect(ctx.destination);
|
|
||||||
}
|
|
||||||
return destination;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const panic = () => {
|
|
||||||
getDestination().gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01);
|
|
||||||
destination = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
let workletsLoading;
|
let workletsLoading;
|
||||||
|
|
||||||
function loadWorklets() {
|
function loadWorklets() {
|
||||||
@ -95,6 +83,39 @@ export async function initAudioOnFirstClick(options) {
|
|||||||
let delays = {};
|
let delays = {};
|
||||||
const maxfeedback = 0.98;
|
const maxfeedback = 0.98;
|
||||||
|
|
||||||
|
let channelMerger, destinationGain;
|
||||||
|
|
||||||
|
// input: AudioNode, channels: ?Array<int>
|
||||||
|
export const connectToDestination = (input, channels = [0, 1]) => {
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
if (channelMerger == null) {
|
||||||
|
channelMerger = new ChannelMergerNode(ctx, { numberOfInputs: ctx.destination.channelCount });
|
||||||
|
destinationGain = new GainNode(ctx);
|
||||||
|
channelMerger.connect(destinationGain);
|
||||||
|
destinationGain.connect(ctx.destination);
|
||||||
|
}
|
||||||
|
//This upmix can be removed if correct channel counts are set throughout the app,
|
||||||
|
// and then strudel could theoretically support surround sound audio files
|
||||||
|
const stereoMix = new StereoPannerNode(ctx);
|
||||||
|
input.connect(stereoMix);
|
||||||
|
|
||||||
|
const splitter = new ChannelSplitterNode(ctx, {
|
||||||
|
numberOfOutputs: stereoMix.channelCount,
|
||||||
|
});
|
||||||
|
stereoMix.connect(splitter);
|
||||||
|
channels.forEach((ch, i) => {
|
||||||
|
splitter.connect(channelMerger, i % stereoMix.channelCount, clamp(ch, 0, ctx.destination.channelCount - 1));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const panic = () => {
|
||||||
|
if (destinationGain == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
destinationGain.gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01);
|
||||||
|
destinationGain = null;
|
||||||
|
};
|
||||||
|
|
||||||
function getDelay(orbit, delaytime, delayfeedback, t) {
|
function getDelay(orbit, delaytime, delayfeedback, t) {
|
||||||
if (delayfeedback > maxfeedback) {
|
if (delayfeedback > maxfeedback) {
|
||||||
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
||||||
@ -104,7 +125,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
|
|||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||||
dly.start?.(t); // for some reason, this throws when audion extension is installed..
|
dly.start?.(t); // for some reason, this throws when audion extension is installed..
|
||||||
dly.connect(getDestination());
|
connectToDestination(dly, [0, 1]);
|
||||||
delays[orbit] = dly;
|
delays[orbit] = dly;
|
||||||
}
|
}
|
||||||
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
|
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
|
||||||
@ -163,7 +184,7 @@ function getReverb(orbit, duration, fade, lp, dim, ir) {
|
|||||||
if (!reverbs[orbit]) {
|
if (!reverbs[orbit]) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
||||||
reverb.connect(getDestination());
|
connectToDestination(reverb, [0, 1]);
|
||||||
reverbs[orbit] = reverb;
|
reverbs[orbit] = reverb;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -241,6 +262,7 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||||||
source,
|
source,
|
||||||
gain = 0.8,
|
gain = 0.8,
|
||||||
postgain = 1,
|
postgain = 1,
|
||||||
|
density = 0.03,
|
||||||
// filters
|
// filters
|
||||||
ftype = '12db',
|
ftype = '12db',
|
||||||
fanchor = 0.5,
|
fanchor = 0.5,
|
||||||
@ -268,7 +290,7 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||||||
bpsustain = 1,
|
bpsustain = 1,
|
||||||
bprelease = 0.01,
|
bprelease = 0.01,
|
||||||
bandq = 1,
|
bandq = 1,
|
||||||
|
channels = [1, 2],
|
||||||
//phaser
|
//phaser
|
||||||
phaser,
|
phaser,
|
||||||
phaserdepth = 0.75,
|
phaserdepth = 0.75,
|
||||||
@ -301,6 +323,10 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||||||
compressorRelease,
|
compressorRelease,
|
||||||
} = value;
|
} = value;
|
||||||
gain = nanFallback(gain, 1);
|
gain = nanFallback(gain, 1);
|
||||||
|
|
||||||
|
//music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior
|
||||||
|
channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||||
|
|
||||||
gain *= velocity; // legacy fix for velocity
|
gain *= velocity; // legacy fix for velocity
|
||||||
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
|
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
|
||||||
const onended = () => {
|
const onended = () => {
|
||||||
@ -434,9 +460,9 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// last gain
|
// last gain
|
||||||
const post = gainNode(postgain);
|
const post = new GainNode(ac, { gain: postgain });
|
||||||
chain.push(post);
|
chain.push(post);
|
||||||
post.connect(getDestination());
|
connectToDestination(post, channels);
|
||||||
|
|
||||||
// delay
|
// delay
|
||||||
let delaySend;
|
let delaySend;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const waveforms = ['sine', 'square', 'triangle', 'sawtooth'];
|
const waveforms = ['sine', 'square', 'triangle', 'sawtooth'];
|
||||||
const noises = ['pink', 'white', 'brown'];
|
const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||||
|
|
||||||
export function registerSynthSounds() {
|
export function registerSynthSounds() {
|
||||||
[...waveforms, ...noises].forEach((s) => {
|
[...waveforms, ...noises].forEach((s) => {
|
||||||
@ -36,7 +36,8 @@ export function registerSynthSounds() {
|
|||||||
if (waveforms.includes(s)) {
|
if (waveforms.includes(s)) {
|
||||||
sound = getOscillator(s, t, value);
|
sound = getOscillator(s, t, value);
|
||||||
} else {
|
} else {
|
||||||
sound = getNoiseOscillator(s, t);
|
let { density } = value;
|
||||||
|
sound = getNoiseOscillator(s, t, density);
|
||||||
}
|
}
|
||||||
|
|
||||||
let { node: o, stop, triggerRelease } = sound;
|
let { node: o, stop, triggerRelease } = sound;
|
||||||
|
|||||||
@ -1038,6 +1038,31 @@ exports[`runs examples > example "ceil" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "channels" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/5 | note:e channels:[3 4] ]",
|
||||||
|
"[ 1/5 → 2/5 | note:a channels:[3 4] ]",
|
||||||
|
"[ 2/5 → 3/5 | note:d channels:[3 4] ]",
|
||||||
|
"[ 3/5 → 4/5 | note:b channels:[3 4] ]",
|
||||||
|
"[ 4/5 → 1/1 | note:g channels:[3 4] ]",
|
||||||
|
"[ 1/1 → 6/5 | note:e channels:[3 4] ]",
|
||||||
|
"[ 6/5 → 7/5 | note:a channels:[3 4] ]",
|
||||||
|
"[ 7/5 → 8/5 | note:d channels:[3 4] ]",
|
||||||
|
"[ 8/5 → 9/5 | note:b channels:[3 4] ]",
|
||||||
|
"[ 9/5 → 2/1 | note:g channels:[3 4] ]",
|
||||||
|
"[ 2/1 → 11/5 | note:e channels:[3 4] ]",
|
||||||
|
"[ 11/5 → 12/5 | note:a channels:[3 4] ]",
|
||||||
|
"[ 12/5 → 13/5 | note:d channels:[3 4] ]",
|
||||||
|
"[ 13/5 → 14/5 | note:b channels:[3 4] ]",
|
||||||
|
"[ 14/5 → 3/1 | note:g channels:[3 4] ]",
|
||||||
|
"[ 3/1 → 16/5 | note:e channels:[3 4] ]",
|
||||||
|
"[ 16/5 → 17/5 | note:a channels:[3 4] ]",
|
||||||
|
"[ 17/5 → 18/5 | note:d channels:[3 4] ]",
|
||||||
|
"[ 18/5 → 19/5 | note:b channels:[3 4] ]",
|
||||||
|
"[ 19/5 → 4/1 | note:g channels:[3 4] ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "chooseCycles" example index 0 1`] = `
|
exports[`runs examples > example "chooseCycles" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | s:bd ]",
|
"[ 0/1 → 1/4 | s:bd ]",
|
||||||
@ -3267,22 +3292,22 @@ exports[`runs examples > example "outside" example index 0 1`] = `
|
|||||||
|
|
||||||
exports[`runs examples > example "palindrome" example index 0 1`] = `
|
exports[`runs examples > example "palindrome" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:g ]",
|
"[ 0/1 → 1/4 | note:c ]",
|
||||||
"[ 1/4 → 1/2 | note:e ]",
|
"[ 1/4 → 1/2 | note:d ]",
|
||||||
"[ 1/2 → 3/4 | note:d ]",
|
"[ 1/2 → 3/4 | note:e ]",
|
||||||
"[ 3/4 → 1/1 | note:c ]",
|
"[ 3/4 → 1/1 | note:g ]",
|
||||||
"[ 1/1 → 5/4 | note:c ]",
|
"[ 1/1 → 5/4 | note:g ]",
|
||||||
"[ 5/4 → 3/2 | note:d ]",
|
"[ 5/4 → 3/2 | note:e ]",
|
||||||
"[ 3/2 → 7/4 | note:e ]",
|
"[ 3/2 → 7/4 | note:d ]",
|
||||||
"[ 7/4 → 2/1 | note:g ]",
|
"[ 7/4 → 2/1 | note:c ]",
|
||||||
"[ 2/1 → 9/4 | note:g ]",
|
"[ 2/1 → 9/4 | note:c ]",
|
||||||
"[ 9/4 → 5/2 | note:e ]",
|
"[ 9/4 → 5/2 | note:d ]",
|
||||||
"[ 5/2 → 11/4 | note:d ]",
|
"[ 5/2 → 11/4 | note:e ]",
|
||||||
"[ 11/4 → 3/1 | note:c ]",
|
"[ 11/4 → 3/1 | note:g ]",
|
||||||
"[ 3/1 → 13/4 | note:c ]",
|
"[ 3/1 → 13/4 | note:g ]",
|
||||||
"[ 13/4 → 7/2 | note:d ]",
|
"[ 13/4 → 7/2 | note:e ]",
|
||||||
"[ 7/2 → 15/4 | note:e ]",
|
"[ 7/2 → 15/4 | note:d ]",
|
||||||
"[ 15/4 → 4/1 | note:g ]",
|
"[ 15/4 → 4/1 | note:c ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -8372,16 +8372,16 @@ exports[`renders tunes > tune: hyperpop 1`] = `
|
|||||||
|
|
||||||
exports[`renders tunes > tune: juxUndTollerei 1`] = `
|
exports[`renders tunes > tune: juxUndTollerei 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:bb3 s:sawtooth pan:0 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 0/1 → 1/4 | note:c3 s:sawtooth pan:0 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ 0/1 → 1/4 | note:c3 s:sawtooth pan:1 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 0/1 → 1/4 | note:bb3 s:sawtooth pan:1 cutoff:1188.2154262966046 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ 1/4 → 1/2 | note:g3 s:sawtooth pan:0 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 1/4 → 1/2 | note:eb3 s:sawtooth pan:0 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ 1/4 → 1/2 | note:eb3 s:sawtooth pan:1 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 1/4 → 1/2 | note:g3 s:sawtooth pan:1 cutoff:1361.2562095290161 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ 1/2 → 3/4 | note:eb3 s:sawtooth pan:0 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 1/2 → 3/4 | note:g3 s:sawtooth pan:0 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ 1/2 → 3/4 | note:g3 s:sawtooth pan:1 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 1/2 → 3/4 | note:eb3 s:sawtooth pan:1 cutoff:1524.257063143398 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ (101/200 → 1/1) ⇝ 201/200 | note:65 s:triangle pan:0 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ (101/200 → 1/1) ⇝ 201/200 | note:55 s:triangle pan:0 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ (101/200 → 1/1) ⇝ 201/200 | note:55 s:triangle pan:1 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ (101/200 → 1/1) ⇝ 201/200 | note:65 s:triangle pan:1 cutoff:1601.4815730092653 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ 3/4 → 1/1 | note:c3 s:sawtooth pan:0 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 3/4 → 1/1 | note:bb3 s:sawtooth pan:0 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
"[ 3/4 → 1/1 | note:bb3 s:sawtooth pan:1 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
"[ 3/4 → 1/1 | note:c3 s:sawtooth pan:1 cutoff:1670.953955747281 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"/home/felix/projects/strudel/packages/core/fraction.mjs": [
|
"/packages/core/fraction.mjs": [
|
||||||
"gcd"
|
"gcd"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/timespan.mjs": [
|
"/packages/core/timespan.mjs": [
|
||||||
"TimeSpan"
|
"TimeSpan"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/hap.mjs": [
|
"/packages/core/hap.mjs": [
|
||||||
"Hap"
|
"Hap"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/state.mjs": [
|
"/packages/core/state.mjs": [
|
||||||
"State"
|
"State"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/util.mjs": [
|
"/packages/core/util.mjs": [
|
||||||
"isNoteWithOctave",
|
"isNoteWithOctave",
|
||||||
"isNote",
|
"isNote",
|
||||||
"tokenizeNote",
|
"tokenizeNote",
|
||||||
@ -34,23 +34,31 @@
|
|||||||
"mapArgs",
|
"mapArgs",
|
||||||
"numeralArgs",
|
"numeralArgs",
|
||||||
"parseFractional",
|
"parseFractional",
|
||||||
"fractionalArgs"
|
"fractionalArgs",
|
||||||
|
"splitAt",
|
||||||
|
"zipWith",
|
||||||
|
"clamp",
|
||||||
|
"sol2note"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/value.mjs": [
|
"/packages/core/value.mjs": [
|
||||||
"unionWithObj",
|
"unionWithObj",
|
||||||
"valued",
|
"valued",
|
||||||
"Value",
|
"Value",
|
||||||
"map"
|
"map"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/drawLine.mjs": [],
|
"/packages/core/drawLine.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/core/logger.mjs": [
|
"/packages/core/logger.mjs": [
|
||||||
"logKey",
|
"logKey",
|
||||||
"logger"
|
"logger"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/pattern.mjs": [
|
"/packages/core/pattern.mjs": [
|
||||||
"setStringParser",
|
"setStringParser",
|
||||||
|
"polyrhythm",
|
||||||
|
"pr",
|
||||||
|
"pm",
|
||||||
"isPattern",
|
"isPattern",
|
||||||
"reify",
|
"reify",
|
||||||
|
"fastcat",
|
||||||
"set",
|
"set",
|
||||||
"keep",
|
"keep",
|
||||||
"keepif",
|
"keepif",
|
||||||
@ -74,22 +82,33 @@
|
|||||||
"func",
|
"func",
|
||||||
"compressSpan",
|
"compressSpan",
|
||||||
"compressspan",
|
"compressspan",
|
||||||
|
"fastgap",
|
||||||
"focusSpan",
|
"focusSpan",
|
||||||
"focusspan",
|
"focusspan",
|
||||||
|
"density",
|
||||||
|
"sparsity",
|
||||||
"zoomArc",
|
"zoomArc",
|
||||||
"zoomarc",
|
"zoomarc",
|
||||||
|
"inv",
|
||||||
|
"juxby",
|
||||||
|
"echowith",
|
||||||
|
"stutWith",
|
||||||
|
"stutwith",
|
||||||
|
"iterback",
|
||||||
|
"chunkback",
|
||||||
"bypass",
|
"bypass",
|
||||||
"duration",
|
"duration",
|
||||||
"color",
|
|
||||||
"colour",
|
"colour",
|
||||||
"striate"
|
"loopat",
|
||||||
|
"loopatcps"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/controls.mjs": [],
|
"/packages/core/controls.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/core/euclid.mjs": [
|
"/packages/core/euclid.mjs": [
|
||||||
|
"bjork",
|
||||||
"euclidrot",
|
"euclidrot",
|
||||||
"euclidLegatoRot"
|
"euclidLegatoRot"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/signal.mjs": [
|
"/packages/core/signal.mjs": [
|
||||||
"steady",
|
"steady",
|
||||||
"signal",
|
"signal",
|
||||||
"isaw",
|
"isaw",
|
||||||
@ -112,34 +131,37 @@
|
|||||||
"degradeByWith",
|
"degradeByWith",
|
||||||
"undegrade"
|
"undegrade"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/speak.mjs": [
|
"/packages/core/speak.mjs": [
|
||||||
"speak"
|
"speak"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/evaluate.mjs": [
|
"/packages/core/evaluate.mjs": [
|
||||||
"evalScope",
|
"evalScope",
|
||||||
"evaluate"
|
"evaluate"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/zyklus.mjs": [],
|
"/packages/core/zyklus.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/core/cyclist.mjs": [
|
"/packages/core/cyclist.mjs": [
|
||||||
"Cyclist"
|
"Cyclist"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/time.mjs": [
|
"/packages/core/time.mjs": [
|
||||||
"getTime",
|
"getTime",
|
||||||
"setTime"
|
"setTime"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/repl.mjs": [
|
"/packages/core/repl.mjs": [
|
||||||
"repl"
|
"repl",
|
||||||
|
"getTrigger"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/draw.mjs": [
|
"/packages/core/draw.mjs": [
|
||||||
"getDrawContext",
|
"getDrawContext",
|
||||||
"cleanupDraw"
|
"cleanupDraw",
|
||||||
|
"Framer",
|
||||||
|
"Drawer"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/animate.mjs": [
|
"/packages/core/animate.mjs": [
|
||||||
"x",
|
"x",
|
||||||
"y",
|
"y",
|
||||||
"w",
|
"w",
|
||||||
"h",
|
"h",
|
||||||
"a",
|
"angle",
|
||||||
"r",
|
"r",
|
||||||
"fill",
|
"fill",
|
||||||
"smear",
|
"smear",
|
||||||
@ -147,85 +169,121 @@
|
|||||||
"moveXY",
|
"moveXY",
|
||||||
"zoomIn"
|
"zoomIn"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/pianoroll.mjs": [
|
"/packages/core/pianoroll.mjs": [
|
||||||
"pianoroll"
|
"getDrawOptions",
|
||||||
|
"drawPianoroll"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/ui.mjs": [
|
"/packages/core/spiral.mjs": [],
|
||||||
|
"/packages/core/ui.mjs": [
|
||||||
"backgroundImage",
|
"backgroundImage",
|
||||||
"cleanupUi"
|
"cleanupUi"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/core/gist.js": [],
|
"/packages/core/gist.js": [],
|
||||||
"/home/felix/projects/strudel/packages/core/index.mjs": [],
|
"/packages/core/index.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/midi/midi.mjs": [
|
"/packages/csound/index.mjs": [
|
||||||
|
"loadCSound",
|
||||||
|
"loadcsound",
|
||||||
|
"loadCsound",
|
||||||
|
"csound",
|
||||||
|
"loadOrc"
|
||||||
|
],
|
||||||
|
"/packages/desktopbridge/utils.mjs": [
|
||||||
|
"Invoke",
|
||||||
|
"isTauri"
|
||||||
|
],
|
||||||
|
"/packages/desktopbridge/midibridge.mjs": [],
|
||||||
|
"/packages/desktopbridge/oscbridge.mjs": [],
|
||||||
|
"/packages/desktopbridge/index.mjs": [],
|
||||||
|
"/packages/midi/midi.mjs": [
|
||||||
"WebMidi",
|
"WebMidi",
|
||||||
"enableWebMidi"
|
"enableWebMidi",
|
||||||
|
"midin"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/midi/index.mjs": [],
|
"/packages/midi/index.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/mini/krill-parser.js": [],
|
"/packages/mini/krill-parser.js": [],
|
||||||
"/home/felix/projects/strudel/packages/mini/mini.mjs": [
|
"/packages/mini/mini.mjs": [
|
||||||
"patternifyAST",
|
"patternifyAST",
|
||||||
|
"getLeafLocation",
|
||||||
|
"mini2ast",
|
||||||
|
"getLeaves",
|
||||||
|
"getLeafLocations",
|
||||||
"mini",
|
"mini",
|
||||||
|
"m",
|
||||||
"h",
|
"h",
|
||||||
"minify"
|
"minify",
|
||||||
|
"miniAllStrings"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/mini/index.mjs": [],
|
"/packages/mini/index.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/soundfonts/fontloader.mjs": [
|
"/packages/soundfonts/gm.mjs": [],
|
||||||
|
"/packages/soundfonts/fontloader.mjs": [
|
||||||
"getFontBufferSource",
|
"getFontBufferSource",
|
||||||
"getFontPitch"
|
"getFontPitch",
|
||||||
|
"registerSoundfonts"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/soundfonts/list.mjs": [
|
"/packages/soundfonts/list.mjs": [
|
||||||
"instruments",
|
"instruments",
|
||||||
"drums",
|
"drums",
|
||||||
"instrumentNames"
|
"instrumentNames"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/soundfonts/sfumato.mjs": [
|
"/packages/soundfonts/sfumato.mjs": [
|
||||||
"loadSoundfont"
|
"loadSoundfont"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/soundfonts/index.mjs": [],
|
"/packages/soundfonts/index.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/tonal/tonal.mjs": [],
|
"/packages/tonal/tonal.mjs": [],
|
||||||
"/home/felix/projects/strudel/packages/tonal/voicings.mjs": [
|
"/packages/tonal/tonleiter.mjs": [
|
||||||
"voicingRegistry",
|
"pc2chroma",
|
||||||
"setVoicingRange"
|
"rotateChroma",
|
||||||
|
"chroma2pc",
|
||||||
|
"tokenizeChord",
|
||||||
|
"note2pc",
|
||||||
|
"note2oct",
|
||||||
|
"note2chroma",
|
||||||
|
"midi2chroma",
|
||||||
|
"pitch2chroma",
|
||||||
|
"step2semitones",
|
||||||
|
"x2midi",
|
||||||
|
"scaleStep",
|
||||||
|
"renderVoicing",
|
||||||
|
"accidentalOffset",
|
||||||
|
"Step",
|
||||||
|
"Note"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/tonal/index.mjs": [],
|
"/packages/tonal/ireal.mjs": [
|
||||||
"/home/felix/projects/strudel/packages/transpiler/transpiler.mjs": [
|
"simple",
|
||||||
|
"complex"
|
||||||
|
],
|
||||||
|
"/packages/tonal/voicings.mjs": [
|
||||||
|
"voicingRegistry",
|
||||||
|
"setVoicingRange",
|
||||||
|
"registerVoicings",
|
||||||
|
"voicingAlias"
|
||||||
|
],
|
||||||
|
"/packages/tonal/index.mjs": [],
|
||||||
|
"/packages/transpiler/transpiler.mjs": [
|
||||||
"transpiler"
|
"transpiler"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/transpiler/index.mjs": [
|
"/packages/transpiler/index.mjs": [
|
||||||
"evaluate"
|
"evaluate"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/webaudio/feedbackdelay.mjs": [],
|
"/packages/webaudio/webaudio.mjs": [
|
||||||
"/home/felix/projects/strudel/packages/webaudio/reverb.mjs": [],
|
"webaudioOutputTrigger",
|
||||||
"/home/felix/projects/strudel/packages/webaudio/sampler.mjs": [
|
|
||||||
"getCachedBuffer",
|
|
||||||
"getSampleBufferSource",
|
|
||||||
"loadBuffer",
|
|
||||||
"reverseBuffer",
|
|
||||||
"getLoadedBuffer",
|
|
||||||
"resetLoadedSamples",
|
|
||||||
"getLoadedSamples"
|
|
||||||
],
|
|
||||||
"/home/felix/projects/strudel/packages/webaudio/vowel.mjs": [
|
|
||||||
"vowelFormant"
|
|
||||||
],
|
|
||||||
"/home/felix/projects/strudel/packages/webaudio/webaudio.mjs": [
|
|
||||||
"getAudioContext",
|
|
||||||
"panic",
|
|
||||||
"initAudio",
|
|
||||||
"initAudioOnFirstClick",
|
|
||||||
"webaudioOutput",
|
"webaudioOutput",
|
||||||
"webaudioOutputTrigger"
|
"webaudioScheduler"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/webaudio/index.mjs": [],
|
"/packages/webaudio/scope.mjs": [
|
||||||
"/home/felix/projects/strudel/packages/xen/xen.mjs": [
|
"drawTimeScope",
|
||||||
|
"drawFrequencyScope"
|
||||||
|
],
|
||||||
|
"/packages/webaudio/index.mjs": [],
|
||||||
|
"/packages/xen/xen.mjs": [
|
||||||
"edo",
|
"edo",
|
||||||
"xen",
|
"xen",
|
||||||
"tuning"
|
"tuning"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/xen/tunejs.js": [],
|
"/packages/xen/tunejs.js": [],
|
||||||
"/home/felix/projects/strudel/packages/xen/tune.mjs": [
|
"/packages/xen/tune.mjs": [
|
||||||
"tune"
|
"tune"
|
||||||
],
|
],
|
||||||
"/home/felix/projects/strudel/packages/xen/index.mjs": [],
|
"/packages/xen/index.mjs": [],
|
||||||
"/home/felix/projects/strudel/index.mjs": []
|
"/index.mjs": []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,24 +13,39 @@ import AstroPWA from '@vite-pwa/astro';
|
|||||||
|
|
||||||
const site = `https://strudel.cc/`; // root url without a path
|
const site = `https://strudel.cc/`; // root url without a path
|
||||||
const base = '/'; // base path of the strudel site
|
const base = '/'; // base path of the strudel site
|
||||||
|
const baseNoTrailing = base.endsWith('/') ? base.slice(0, -1) : base;
|
||||||
|
|
||||||
// this rehype plugin converts relative anchor links to absolute ones
|
// this rehype plugin fixes relative links
|
||||||
// it wokrs by prepending the absolute page path to anchor links
|
// it works by prepending the base + page path to anchor links
|
||||||
// example: #gain -> /learn/effects/#gain
|
// and by prepending the base path to other relative links starting with /
|
||||||
// this is necessary when using a base href like <base href={base} />
|
// this is necessary when using a base href like <base href={base} />
|
||||||
// in this setup, relative anchor links will always link to base, instead of the current page
|
// examples with base as "mybase":
|
||||||
function absoluteAnchors() {
|
// #gain -> /mybase/learn/effects/#gain
|
||||||
|
// /some/page -> /mybase/some/page
|
||||||
|
function relativeURLFix() {
|
||||||
return (tree, file) => {
|
return (tree, file) => {
|
||||||
const chunks = file.history[0].split('/src/pages/'); // file.history[0] is the file path
|
const chunks = file.history[0].split('/src/pages/'); // file.history[0] is the file path
|
||||||
const path = chunks[chunks.length - 1].slice(0, -4); // only path inside src/pages, without .mdx
|
const path = chunks[chunks.length - 1].slice(0, -4); // only path inside src/pages, without .mdx
|
||||||
return rehypeUrls((url) => {
|
return rehypeUrls((url) => {
|
||||||
if (!url.href.startsWith('#')) {
|
let newHref = baseNoTrailing;
|
||||||
|
if (url.href.startsWith('#')) {
|
||||||
|
// special case: a relative anchor link to the current page
|
||||||
|
newHref += `/${path}/${url.href}`;
|
||||||
|
} else if (url.href.startsWith('/')) {
|
||||||
|
// any other relative url starting with /
|
||||||
|
newHref += url.pathname;
|
||||||
|
if (url.pathname.indexOf('.') == -1) {
|
||||||
|
// append trailing slash to resource only if there is no file extension
|
||||||
|
newHref += url.pathname.endsWith('/') ? '' : '/';
|
||||||
|
}
|
||||||
|
newHref += url.search || '';
|
||||||
|
newHref += url.hash || '';
|
||||||
|
} else {
|
||||||
|
// leave this URL alone
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const baseWithSlash = base.endsWith('/') ? base : base + '/';
|
// console.log(url.href + ' -> ', newHref);
|
||||||
const absoluteUrl = baseWithSlash + path + url.href;
|
return newHref;
|
||||||
// console.log(url.href + ' -> ', absoluteUrl);
|
|
||||||
return absoluteUrl;
|
|
||||||
})(tree);
|
})(tree);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -40,7 +55,7 @@ const options = {
|
|||||||
remarkToc,
|
remarkToc,
|
||||||
// E.g. `remark-frontmatter`
|
// E.g. `remark-frontmatter`
|
||||||
],
|
],
|
||||||
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'append' }], absoluteAnchors],
|
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'append' }], relativeURLFix],
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { pwaInfo } from 'virtual:pwa-info';
|
|||||||
import '../styles/index.css';
|
import '../styles/index.css';
|
||||||
|
|
||||||
const { BASE_URL } = import.meta.env;
|
const { BASE_URL } = import.meta.env;
|
||||||
const base = BASE_URL;
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Global Metadata -->
|
<!-- Global Metadata -->
|
||||||
@ -11,20 +11,20 @@ const base = BASE_URL;
|
|||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href={`${baseNoTrailing}/favicon.ico`} />
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Strudel is a music live coding environment for the browser, porting the TidalCycles pattern language to JavaScript."
|
content="Strudel is a music live coding environment for the browser, porting the TidalCycles pattern language to JavaScript."
|
||||||
/>
|
/>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href={`${baseNoTrailing}/favicon.ico`} />
|
||||||
<link rel="apple-touch-icon" href="/icons/apple-icon-180.png" sizes="180x180" />
|
<link rel="apple-touch-icon" href={`${baseNoTrailing}/icons/apple-icon-180.png`} sizes="180x180" />
|
||||||
<meta name="theme-color" content="#222222" />
|
<meta name="theme-color" content="#222222" />
|
||||||
|
|
||||||
<base href={base} />
|
<base href={BASE_URL} />
|
||||||
|
|
||||||
<!-- Scrollable a11y code helper -->
|
<!-- Scrollable a11y code helper -->
|
||||||
<script src="./make-scrollable-code-focusable.js" is:inline></script>
|
<script src{`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
|
||||||
|
|
||||||
<script src="/src/pwa.ts"></script>
|
<script src="/src/pwa.ts"></script>
|
||||||
<!-- this does not work for some reason: -->
|
<!-- this does not work for some reason: -->
|
||||||
|
|||||||
@ -16,6 +16,9 @@ const { currentPage } = Astro.props as Props;
|
|||||||
// const lang = getLanguageFromURL(currentPage);
|
// const lang = getLanguageFromURL(currentPage);
|
||||||
const langCode = 'en'; // getLanguageFromURL(currentPage);
|
const langCode = 'en'; // getLanguageFromURL(currentPage);
|
||||||
const sidebar = SIDEBAR[langCode];
|
const sidebar = SIDEBAR[langCode];
|
||||||
|
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
---
|
---
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
@ -23,7 +26,7 @@ const sidebar = SIDEBAR[langCode];
|
|||||||
title="Top Navigation"
|
title="Top Navigation"
|
||||||
>
|
>
|
||||||
<div class="flex overflow-visible items-center grow" style="overflow:visible">
|
<div class="flex overflow-visible items-center grow" style="overflow:visible">
|
||||||
<a href="/" class="flex items-center text-2xl space-x-2">
|
<a href={`${baseNoTrailing}/`} class="flex items-center text-2xl space-x-2">
|
||||||
<h1 class="font-bold flex space-x-2 items-baseline text-xl">
|
<h1 class="font-bold flex space-x-2 items-baseline text-xl">
|
||||||
<span>🌀</span>
|
<span>🌀</span>
|
||||||
<div class="flex space-x-1 items-baseline">
|
<div class="flex space-x-1 items-baseline">
|
||||||
@ -35,9 +38,9 @@ const sidebar = SIDEBAR[langCode];
|
|||||||
</div>
|
</div>
|
||||||
{/* KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle /> */}
|
{/* KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle /> */}
|
||||||
<div class="search-item h-10">
|
<div class="search-item h-10">
|
||||||
<!-- <Search client:idle /> -->
|
<Search client:idle />
|
||||||
</div>
|
</div>
|
||||||
<a href="./" class="hidden md:flex cursor-pointer items-center space-x-1"
|
<a href={`${baseNoTrailing}/`} class="hidden md:flex cursor-pointer items-center space-x-1"
|
||||||
><CommandLineIcon className="w-5 h-5" /><span>go to REPL</span>
|
><CommandLineIcon className="w-5 h-5" /><span>go to REPL</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="md:hidden">
|
<div class="md:hidden">
|
||||||
@ -48,8 +51,8 @@ const sidebar = SIDEBAR[langCode];
|
|||||||
<style>
|
<style>
|
||||||
/** Style Algolia */
|
/** Style Algolia */
|
||||||
:root {
|
:root {
|
||||||
--docsearch-primary-color: var(--theme-accent);
|
--docsearch-primary-color: var(--lineHighlight);
|
||||||
--docsearch-logo-color: var(--theme-text);
|
--docsearch-logo-color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-item {
|
.search-item {
|
||||||
|
|||||||
@ -1,7 +1,28 @@
|
|||||||
/** Style Algolia */
|
/** Style Algolia */
|
||||||
:root {
|
:root {
|
||||||
--docsearch-primary-color: var(--theme-accent);
|
--docsearch-primary-color: var(--lineHighlight);
|
||||||
--docsearch-logo-color: var(--theme-text);
|
--docsearch-logo-color: var(--foreground);
|
||||||
|
--docsearch-container-background: rgba(110, 110, 110, 0.8);
|
||||||
|
--docsearch-modal-shadow: none;
|
||||||
|
--docsearch-text-color: var(--foreground);
|
||||||
|
--docsearch-highlight-color: var(--foreground);
|
||||||
|
--docsearch-searchbox-background: var(--lineBackground);
|
||||||
|
--docsearch-searchbox-focus-background: var(--lineBackground);
|
||||||
|
--docsearch-searchbox-shadow: inset 0 0 0 2px var(--lineHighlight);
|
||||||
|
--docsearch-hit-background: var(--background);
|
||||||
|
--docsearch-hit-active-color: var(--background);
|
||||||
|
--docsearch-hit-color: var(--foreground);
|
||||||
|
--docsearch-hit-shadow: 0 1px 3px 0 var(--foreground);
|
||||||
|
--docsearch-footer-shadow: none;
|
||||||
|
--docsearch-footer-background: var(--gutterBackground);
|
||||||
|
--docsearch-modal-background: var(--background);
|
||||||
|
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #fff 30%);
|
||||||
|
--docsearch-key-gradient: var(--foreground);
|
||||||
|
--docsearch-key-shadow: inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground),
|
||||||
|
0 1px 2px 1px var(--gutterBackground);
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--docsearch-muted-color: color-mix(in srgb, var(--foreground), #000 30%);
|
||||||
}
|
}
|
||||||
.search-input {
|
.search-input {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@ -14,9 +35,9 @@
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
background-color: var(--theme-divider);
|
background-color: var(--lineBackground);
|
||||||
border-color: var(--theme-divider);
|
border-color: var(--lineBackground);
|
||||||
color: var(--theme-text-light);
|
color: var(--foreground);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
@ -29,15 +50,15 @@
|
|||||||
}
|
}
|
||||||
.search-input:hover,
|
.search-input:hover,
|
||||||
.search-input:focus {
|
.search-input:focus {
|
||||||
color: var(--theme-text);
|
color: var(--foreground);
|
||||||
border-color: var(--theme-text-light);
|
border-color: var(--foreground);
|
||||||
}
|
}
|
||||||
.search-input:hover::placeholder,
|
.search-input:hover::placeholder,
|
||||||
.search-input:focus::placeholder {
|
.search-input:focus::placeholder {
|
||||||
color: var(--theme-text-light);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
.search-input::placeholder {
|
.search-input::placeholder {
|
||||||
color: var(--theme-text-light);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
.search-hint {
|
.search-hint {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -48,11 +69,13 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
letter-spacing: 0.125em;
|
letter-spacing: 0.125em;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
border-color: var(--theme-text-lighter);
|
border-color: transparent;
|
||||||
color: var(--theme-text-light);
|
color: var(--background);
|
||||||
|
background-color: var(--foreground);
|
||||||
|
box-shadow: var(--docsearch-key-shadow);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
@ -64,12 +87,40 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.search-button {
|
||||||
|
background-color: var(--lineBackground) !important;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ *\
|
/* ------------------------------------------------------------ *\
|
||||||
DocSearch (Algolia)
|
DocSearch (Algolia)
|
||||||
\* ------------------------------------------------------------ */
|
\* ------------------------------------------------------------ */
|
||||||
|
.DocSearch-Form {
|
||||||
|
padding: 3px var(--docsearch-spacing);
|
||||||
|
}
|
||||||
.DocSearch-Modal .DocSearch-Hit a {
|
.DocSearch-Modal .DocSearch-Hit a {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: 1px solid var(--theme-accent);
|
border: none;
|
||||||
|
}
|
||||||
|
#docsearch-input.DocSearch-Input {
|
||||||
|
background-color: var(--lineBackground);
|
||||||
|
border-top: 3px solid var(--docsearch-searchbox-shadow);
|
||||||
|
border-bottom: 3px solid var(--docsearch-searchbox-shadow);
|
||||||
|
}
|
||||||
|
.DocSearch-Commands-Key {
|
||||||
|
color: var(--background);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.DocSearch-SearchBar {
|
||||||
|
padding-bottom: 1px;
|
||||||
|
}
|
||||||
|
#docsearch-input.DocSearch-Input:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.DocSearch-Footer {
|
||||||
|
color: var(--docsearch-muted-color) !important;
|
||||||
|
}
|
||||||
|
.DocSearch-Logo svg .cls-1,
|
||||||
|
.DocSearch-Logo svg .cls-2 {
|
||||||
|
fill: var(--docsearch-logo-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import './Search.css';
|
|||||||
|
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import * as docSearchReact from '@docsearch/react';
|
import * as docSearchReact from '@docsearch/react';
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
|
||||||
/** FIXME: This is still kinda nasty, but DocSearch is not ESM ready. */
|
/** FIXME: This is still kinda nasty, but DocSearch is not ESM ready. */
|
||||||
const DocSearchModal = docSearchReact.DocSearchModal || (docSearchReact as any).default.DocSearchModal;
|
const DocSearchModal = docSearchReact.DocSearchModal || (docSearchReact as any).default.DocSearchModal;
|
||||||
@ -43,7 +45,12 @@ export default function Search() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button type="button" ref={searchButtonRef} onClick={onOpen} className="rounded-md bg-slate-900 w-full px-2">
|
<button
|
||||||
|
type="button"
|
||||||
|
ref={searchButtonRef}
|
||||||
|
onClick={onOpen}
|
||||||
|
className="rounded-md bg-slate-900 w-full px-2 search-button"
|
||||||
|
>
|
||||||
<svg width="24" height="24" fill="none">
|
<svg width="24" height="24" fill="none">
|
||||||
<path
|
<path
|
||||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||||
@ -74,6 +81,9 @@ export default function Search() {
|
|||||||
indexName={ALGOLIA.indexName}
|
indexName={ALGOLIA.indexName}
|
||||||
appId={ALGOLIA.appId}
|
appId={ALGOLIA.appId}
|
||||||
apiKey={ALGOLIA.apiKey}
|
apiKey={ALGOLIA.apiKey}
|
||||||
|
getMissingResultsUrl={({ query }) => {
|
||||||
|
return `https://github.com/tidalcycles/strudel/issues/new?title=Missing doc for ${query}`;
|
||||||
|
}}
|
||||||
transformItems={(items) => {
|
transformItems={(items) => {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
// We transform the absolute URL into a relative URL to
|
// We transform the absolute URL into a relative URL to
|
||||||
@ -81,9 +91,13 @@ export default function Search() {
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = item.url;
|
a.href = item.url;
|
||||||
const hash = a.hash === '#overview' ? '' : a.hash;
|
const hash = a.hash === '#overview' ? '' : a.hash;
|
||||||
|
let pathname = a.pathname;
|
||||||
|
pathname = pathname.startsWith('/') ? pathname.slice(1) : pathname;
|
||||||
|
pathname = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||||
|
const url = `${baseNoTrailing}/${pathname}/${hash}`;
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
url: `${a.pathname}${hash}`,
|
url,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ type Props = {
|
|||||||
const { currentPage } = Astro.props as Props;
|
const { currentPage } = Astro.props as Props;
|
||||||
const { BASE_URL } = import.meta.env;
|
const { BASE_URL } = import.meta.env;
|
||||||
let currentPageMatch = currentPage.slice(BASE_URL.length, currentPage.endsWith('/') ? -1 : undefined);
|
let currentPageMatch = currentPage.slice(BASE_URL.length, currentPage.endsWith('/') ? -1 : undefined);
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
|
||||||
const langCode = getLanguageFromURL(currentPage) || 'en';
|
const langCode = getLanguageFromURL(currentPage) || 'en';
|
||||||
const sidebar = SIDEBAR[langCode];
|
const sidebar = SIDEBAR[langCode];
|
||||||
@ -23,7 +24,7 @@ const sidebar = SIDEBAR[langCode];
|
|||||||
<h2>{header}</h2>
|
<h2>{header}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{children.map((child) => {
|
{children.map((child) => {
|
||||||
const url = Astro.site?.pathname + child.link;
|
const url = `${baseNoTrailing}/${child.link}${child.link.endsWith('/') ? '' : '/'}`;
|
||||||
return (
|
return (
|
||||||
<li class="">
|
<li class="">
|
||||||
<a
|
<a
|
||||||
|
|||||||
@ -73,7 +73,7 @@ const TableOfContents: FC<{ headings: MarkdownHeading[]; currentPage: string }>
|
|||||||
.map((heading, i) => (
|
.map((heading, i) => (
|
||||||
<li className={`w-full`} key={i}>
|
<li className={`w-full`} key={i}>
|
||||||
<a
|
<a
|
||||||
href={`${currentPage}#${heading.slug}`}
|
href={`${currentPage}/#${heading.slug}`}
|
||||||
onClick={onLinkClick}
|
onClick={onLinkClick}
|
||||||
className={`py-0.5 block cursor-pointer w-full border-l-4 border-lineHighlight hover:bg-lineHighlight ${
|
className={`py-0.5 block cursor-pointer w-full border-l-4 border-lineHighlight hover:bg-lineHighlight ${
|
||||||
['pl-4', 'pl-9', 'pl-12'][heading.depth - minDepth]
|
['pl-4', 'pl-9', 'pl-12'][heading.depth - minDepth]
|
||||||
|
|||||||
@ -2,19 +2,15 @@ import jsdoc from '../../../doc.json'; // doc.json is built with `npm run jsdoc-
|
|||||||
const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {});
|
const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {});
|
||||||
import { MiniRepl } from './MiniRepl';
|
import { MiniRepl } from './MiniRepl';
|
||||||
|
|
||||||
const getTag = (title, item) => item.tags?.find((t) => t.title === title)?.text;
|
|
||||||
|
|
||||||
export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) {
|
export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) {
|
||||||
const item = docs[name];
|
const item = docs[name];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
console.warn('Not found: ' + name);
|
console.warn('Not found: ' + name);
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
const synonyms = getTag('synonyms', item)?.split(', ') || [];
|
|
||||||
const CustomHeading = `h${h}`;
|
const CustomHeading = `h${h}`;
|
||||||
const description =
|
const description =
|
||||||
item.description?.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => {
|
item.description?.replaceAll(/\{@link ([a-zA-Z\.]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => {
|
||||||
// console.log(_, 'a', a, 'b', b);
|
|
||||||
return `<a href="#${a.replaceAll('.', '').toLowerCase()}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`;
|
return `<a href="#${a.replaceAll('.', '').toLowerCase()}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`;
|
||||||
}) || '';
|
}) || '';
|
||||||
return (
|
return (
|
||||||
@ -22,9 +18,9 @@ export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight })
|
|||||||
{!!h && <CustomHeading>{item.longname}</CustomHeading>}
|
{!!h && <CustomHeading>{item.longname}</CustomHeading>}
|
||||||
{!hideDescription && (
|
{!hideDescription && (
|
||||||
<>
|
<>
|
||||||
{!!synonyms.length && (
|
{!!item.synonyms_text && (
|
||||||
<span>
|
<span>
|
||||||
Synonyms: <code>{synonyms.join(', ')}</code>
|
Synonyms: <code>{item.synonyms_text}</code>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div dangerouslySetInnerHTML={{ __html: description }} />
|
<div dangerouslySetInnerHTML={{ __html: description }} />
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { MiniRepl } from '../../../docs/MiniRepl';
|
|||||||
|
|
||||||
# Willkommen
|
# Willkommen
|
||||||
|
|
||||||
<img src="/icons/strudel_icon.png" className="w-32 animate-pulse md:float-right ml-8" />
|
<div className="w-32 animate-pulse md:float-right ml-8"></div>
|
||||||
|
|
||||||
Willkommen zum Strudel Workshop!
|
Willkommen zum Strudel Workshop!
|
||||||
Du hast den richtigen Ort gefunden wenn du lernen möchtest wie man mit Code Musik macht.
|
Du hast den richtigen Ort gefunden wenn du lernen möchtest wie man mit Code Musik macht.
|
||||||
|
|||||||
@ -3,6 +3,9 @@ import * as tunes from '../../../src/repl/tunes.mjs';
|
|||||||
import HeadCommon from '../../components/HeadCommon.astro';
|
import HeadCommon from '../../components/HeadCommon.astro';
|
||||||
|
|
||||||
import { getMetadata } from '../metadata_parser';
|
import { getMetadata } from '../metadata_parser';
|
||||||
|
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
---
|
---
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
@ -12,11 +15,11 @@ import { getMetadata } from '../metadata_parser';
|
|||||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-2 p-2 select-none">
|
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-2 p-2 select-none">
|
||||||
{
|
{
|
||||||
Object.entries(tunes).map(([name, tune]) => (
|
Object.entries(tunes).map(([name, tune]) => (
|
||||||
<a class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative" href={`./#${btoa(tune)}`}>
|
<a class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative" href={`${baseNoTrailing}/#${btoa(tune)}`}>
|
||||||
<div class="absolute w-full h-full flex justify-center items-center">
|
<div class="absolute w-full h-full flex justify-center items-center">
|
||||||
<span class="bg-slate-800 p-2 rounded-md text-white">{getMetadata(tune)['title'] || name}</span>
|
<span class="bg-slate-800 p-2 rounded-md text-white">{getMetadata(tune)['title'] || name}</span>
|
||||||
</div>
|
</div>
|
||||||
<img src={`./img/example-${name}.png`} />
|
<img src={`${baseNoTrailing}/img/example-${name}.png`} />
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { evaluate } from '@strudel.cycles/transpiler';
|
|||||||
import '../../../../test/runtime.mjs';
|
import '../../../../test/runtime.mjs';
|
||||||
import * as tunes from '../../repl/tunes.mjs';
|
import * as tunes from '../../repl/tunes.mjs';
|
||||||
|
|
||||||
export async function get({ params, request }) {
|
export async function GET({ params, request }) {
|
||||||
const { name } = params;
|
const { name } = params;
|
||||||
const tune = tunes[name];
|
const tune = tunes[name];
|
||||||
const { pattern } = await evaluate(tune);
|
const { pattern } = await evaluate(tune);
|
||||||
@ -13,10 +13,7 @@ export async function get({ params, request }) {
|
|||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
pianoroll({ time: 4, haps, ctx, playhead: 1, fold: 1, background: 'transparent', playheadColor: 'transparent' });
|
pianoroll({ time: 4, haps, ctx, playhead: 1, fold: 1, background: 'transparent', playheadColor: 'transparent' });
|
||||||
const buffer = canvas.toBuffer('image/png');
|
const buffer = canvas.toBuffer('image/png');
|
||||||
return {
|
return new Response(buffer);
|
||||||
body: buffer,
|
|
||||||
encoding: 'binary',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export function getStaticPaths() {
|
export function getStaticPaths() {
|
||||||
return Object.keys(tunes).map((name) => ({
|
return Object.keys(tunes).map((name) => ({
|
||||||
|
|||||||
@ -145,7 +145,7 @@ In both cases, p4 is derived from the value of `freq` or `note`.
|
|||||||
## Limitations / Future Plans
|
## Limitations / Future Plans
|
||||||
|
|
||||||
Apart from the above listed p values, no other parameter can be patterned so far.
|
Apart from the above listed p values, no other parameter can be patterned so far.
|
||||||
This also means that [audio effects](./learn/effects) will not work.
|
This also means that [audio effects](/learn/effects/) will not work.
|
||||||
In the future, the integration could be improved by passing all patterned control parameters to the csound instrument.
|
In the future, the integration could be improved by passing all patterned control parameters to the csound instrument.
|
||||||
This could work by a unique [channel](https://kunstmusik.github.io/icsc2022-csound-web/tutorial2-interacting-with-csound/#step-4---writing-continuous-data-channels)
|
This could work by a unique [channel](https://kunstmusik.github.io/icsc2022-csound-web/tutorial2-interacting-with-csound/#step-4---writing-continuous-data-channels)
|
||||||
for each value. Channels could be read [like this](https://github.com/csound/csound/blob/master/Android/CsoundForAndroid/CsoundAndroidExamples/src/main/res/raw/multitouch_xy.csd).
|
for each value. Channels could be read [like this](https://github.com/csound/csound/blob/master/Android/CsoundForAndroid/CsoundAndroidExamples/src/main/res/raw/multitouch_xy.csd).
|
||||||
|
|||||||
@ -41,7 +41,7 @@ This interactive tutorial will guide you through the basics of Strudel.
|
|||||||
To see and hear what Strudel can do, visit the [Strudel REPL](https://strudel.cc/) and click the Shuffle icon in the top menu bar.
|
To see and hear what Strudel can do, visit the [Strudel REPL](https://strudel.cc/) and click the Shuffle icon in the top menu bar.
|
||||||
You can get a feel for Strudel by browsing and editing these examples and clicking the Refresh icon to update.
|
You can get a feel for Strudel by browsing and editing these examples and clicking the Refresh icon to update.
|
||||||
|
|
||||||
You can also browse through the examples [here](./examples).
|
You can also browse through the examples [here](/examples).
|
||||||
|
|
||||||
Alternatively, you can get a taste of what Strudel can do by clicking play on this track:
|
Alternatively, you can get a taste of what Strudel can do by clicking play on this track:
|
||||||
|
|
||||||
|
|||||||
@ -41,6 +41,8 @@ note("[a,c,e,<a4 ab4 g4 gb4>,b4]/4").s("sawtooth").vib(2)
|
|||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
## H patterns
|
||||||
|
|
||||||
There is a special function `H` that allows you to use a pattern as an input to hydra:
|
There is a special function `H` that allows you to use a pattern as an input to hydra:
|
||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
@ -48,8 +50,50 @@ There is a special function `H` that allows you to use a pattern as an input to
|
|||||||
tune={`await initHydra()
|
tune={`await initHydra()
|
||||||
let pattern = "3 4 5 [6 7]*2"
|
let pattern = "3 4 5 [6 7]*2"
|
||||||
shape(H(pattern)).out(o0)
|
shape(H(pattern)).out(o0)
|
||||||
n(pattern).scale("A:minor").piano().room(1)
|
n(pattern).scale("A:minor").piano().room(1)
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
## detectAudio
|
||||||
|
|
||||||
|
To use hydra audio capture, call `initHydra` with `{detectAudio:true}` configuration param:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:only="react"
|
||||||
|
tune={`await initHydra({detectAudio:true})
|
||||||
|
let pattern = "<3 4 5 [6 7]*2>"
|
||||||
|
shape(H(pattern)).repeat()
|
||||||
|
.scrollY(
|
||||||
|
()=> a.fft[0]*.25
|
||||||
|
)
|
||||||
|
.add(src(o0).color(.71 ).scrollX(.005),.95)
|
||||||
|
.out(o0)
|
||||||
|
n(pattern).scale("A:minor").piano().room(1)
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
You might now be able to see this properly here: [open in REPL](/#YXdhaXQgaW5pdEh5ZHJhKCkKbGV0IHBhdHRlcm4gPSAiMyA0IDUgWzYgN10qMiIKc2hhcGUoSChwYXR0ZXJuKSkub3V0KG8wKQpuKHBhdHRlcm4pLnNjYWxlKCJBOm1pbm9yIikucGlhbm8oKS5yb29tKDEpIA%3D%3D)
|
You might now be able to see this properly here: [open in REPL](/#YXdhaXQgaW5pdEh5ZHJhKCkKbGV0IHBhdHRlcm4gPSAiMyA0IDUgWzYgN10qMiIKc2hhcGUoSChwYXR0ZXJuKSkub3V0KG8wKQpuKHBhdHRlcm4pLnNjYWxlKCJBOm1pbm9yIikucGlhbm8oKS5yb29tKDEpIA%3D%3D)
|
||||||
|
|
||||||
|
Similar to `detectAudio`, all the [available hydra options](https://github.com/hydra-synth/hydra-synth#api) can be passed to `initHydra`.
|
||||||
|
|
||||||
|
## feedStrudel
|
||||||
|
|
||||||
|
Using the `feedStrudel` option, you can transform strudel visualizations with hydra:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:only="react"
|
||||||
|
tune={`await initHydra({feedStrudel:1})
|
||||||
|
//
|
||||||
|
src(s0).kaleid(H("<4 5 6>"))
|
||||||
|
.diff(osc(1,0.5,5))
|
||||||
|
.modulateScale(osc(2,-0.25,1))
|
||||||
|
.out()
|
||||||
|
//
|
||||||
|
stack(
|
||||||
|
s("bd*2,[hh:0:<.5 1>]*4,~ rim").bank("RolandTR909").speed(.9),
|
||||||
|
note("[<g1!3 <bb1 <f1 d1>>>]*3").s("sawtooth")
|
||||||
|
.room(.75).sometimes(add(note(12))).clip(.3)
|
||||||
|
.lpa(.05).lpenv(-4).lpf(2000).lpq(8).ftype('24db')
|
||||||
|
).fft(4)
|
||||||
|
.scope({pos:0,smear:.95})`}
|
||||||
|
/>
|
||||||
|
|||||||
@ -49,7 +49,7 @@ A standalone app has its own desktop / homescreen icon and launches in a separat
|
|||||||
without the browser ui.
|
without the browser ui.
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img src="./pwa/strudel-macos.png" alt="Strudel on MacOS" />
|

|
||||||
<figcaption>Strudel on MacOS</figcaption>
|
<figcaption>Strudel on MacOS</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ Without a chromium based browser, you can use [nativefier](https://github.com/na
|
|||||||
2. run `npx nativefier strudel.cc`
|
2. run `npx nativefier strudel.cc`
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img src="./pwa/strudel-linux.png" alt="Strudel on Linux" />
|

|
||||||
<figcaption>Strudel on Linux</figcaption>
|
<figcaption>Strudel on Linux</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ You can learn more about both of these approaches in the pages [Synths](/learn/s
|
|||||||
|
|
||||||
# Combining notes and sounds
|
# Combining notes and sounds
|
||||||
|
|
||||||
In both of the above cases, we are no longer directly controlling the `note`/`freq` of the sound heard via `s`, as we were in the [Notes](/strudel/notes) page.
|
In both of the above cases, we are no longer directly controlling the `note`/`freq` of the sound heard via `s`, as we were in the [Notes](/workshop/first-notes/) page.
|
||||||
|
|
||||||
So how can we both control the sound and the pitch? We can _combine_ `note`/`freq` with `s` to change the sound of our pitches:
|
So how can we both control the sound and the pitch? We can _combine_ `note`/`freq` with `s` to change the sound of our pitches:
|
||||||
|
|
||||||
|
|||||||
@ -96,13 +96,13 @@ If you find a tidal control that's not on the list, please tell!
|
|||||||
## Sound
|
## Sound
|
||||||
|
|
||||||
Tidal is commonly paired with Superdirt / Supercollider for sound generation.
|
Tidal is commonly paired with Superdirt / Supercollider for sound generation.
|
||||||
While Strudel also has a way of [communicating with Superdirt](./learn/input-output),
|
While Strudel also has a way of [communicating with Superdirt](/learn/input-output/),
|
||||||
it aims to provide a standalone live coding environment that runs entirely in the browser.
|
it aims to provide a standalone live coding environment that runs entirely in the browser.
|
||||||
|
|
||||||
### Audio Effects
|
### Audio Effects
|
||||||
|
|
||||||
Many of SuperDirt's effects have been reimplemented in Strudel, using the Web Audio API.
|
Many of SuperDirt's effects have been reimplemented in Strudel, using the Web Audio API.
|
||||||
You can find a [list of available effects here](./learn/effects).
|
You can find a [list of available effects here](/learn/effects/).
|
||||||
|
|
||||||
### Sampler
|
### Sampler
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,10 @@ Some amount of pink noise can also be added to any oscillator by using the `nois
|
|||||||
|
|
||||||
<MiniRepl client:idle tune={`note("c3").noise("<0.1 0.25 0.5>").scope()`} />
|
<MiniRepl client:idle tune={`note("c3").noise("<0.1 0.25 0.5>").scope()`} />
|
||||||
|
|
||||||
|
You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter:
|
||||||
|
|
||||||
|
<MiniRepl client:idle tune={`s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)).scope()`} />
|
||||||
|
|
||||||
### Additive Synthesis
|
### Additive Synthesis
|
||||||
|
|
||||||
To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:
|
To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:
|
||||||
|
|||||||
@ -110,4 +110,4 @@ Some of these have equivalent operators in the Mini Notation:
|
|||||||
|
|
||||||
<JsDoc client:idle name="ribbon" h={0} />
|
<JsDoc client:idle name="ribbon" h={0} />
|
||||||
|
|
||||||
Apart from modifying time, there are ways to [Control Parameters](functions/value-modifiers).
|
Apart from modifying time, there are ways to [Control Parameters](/functions/value-modifiers/).
|
||||||
|
|||||||
@ -5,6 +5,9 @@ import { Content } from '../../../../my-patterns/README.md';
|
|||||||
import HeadCommon from '../../components/HeadCommon.astro';
|
import HeadCommon from '../../components/HeadCommon.astro';
|
||||||
|
|
||||||
const myPatterns = await getMyPatterns();
|
const myPatterns = await getMyPatterns();
|
||||||
|
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
---
|
---
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
@ -23,12 +26,12 @@ const myPatterns = await getMyPatterns();
|
|||||||
Object.entries(myPatterns).map(([name, tune]) => (
|
Object.entries(myPatterns).map(([name, tune]) => (
|
||||||
<a
|
<a
|
||||||
class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative"
|
class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative"
|
||||||
href={`./#${btoa(tune as string)}`}
|
href={`${baseNoTrailing}/#${btoa(tune as string)}`}
|
||||||
>
|
>
|
||||||
<div class="absolute w-full h-full flex justify-center items-center">
|
<div class="absolute w-full h-full flex justify-center items-center">
|
||||||
<span class="bg-slate-800 p-2 rounded-md text-white">{name}</span>
|
<span class="bg-slate-800 p-2 rounded-md text-white">{name}</span>
|
||||||
</div>
|
</div>
|
||||||
<img src={`./swatch/${name}.png`} />
|
<img src={`${baseNoTrailing}/swatch/${name}.png`} />
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -159,7 +159,7 @@ Try adding more sounds inside a bracket!
|
|||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
Similar to the whole sequence, the content of a sub-sequence will be squished to the its own length.
|
Similar to the whole sequence, the content of a sub-sequence will be squished to its own length.
|
||||||
|
|
||||||
**Multiplication: Speed things up**
|
**Multiplication: Speed things up**
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
|
|||||||
|
|
||||||
# Welcome
|
# Welcome
|
||||||
|
|
||||||
<img src="/icons/strudel_icon.png" className="w-32 animate-pulse md:float-right ml-8" />
|
<div className="w-32 animate-pulse md:float-right ml-8"></div>
|
||||||
|
|
||||||
Welcome to the Strudel documentation pages!
|
Welcome to the Strudel documentation pages!
|
||||||
You've come to the right place if you want to learn how to make music with code.
|
You've come to the right place if you want to learn how to make music with code.
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import React, { useContext } from 'react';
|
|||||||
import { useSettings, setIsZen } from '../settings.mjs';
|
import { useSettings, setIsZen } from '../settings.mjs';
|
||||||
// import { ReplContext } from './Repl';
|
// import { ReplContext } from './Repl';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
|
||||||
export function Header({ context }) {
|
export function Header({ context }) {
|
||||||
const {
|
const {
|
||||||
@ -123,7 +125,7 @@ export function Header({ context }) {
|
|||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<a
|
<a
|
||||||
title="learn"
|
title="learn"
|
||||||
href="./workshop/getting-started"
|
href={`${baseNoTrailing}/workshop/getting-started/`}
|
||||||
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||||
>
|
>
|
||||||
<AcademicCapIcon className="w-6 h-6" />
|
<AcademicCapIcon className="w-6 h-6" />
|
||||||
|
|||||||
@ -37,6 +37,11 @@ export function Reference() {
|
|||||||
{visibleFunctions.map((entry, i) => (
|
{visibleFunctions.map((entry, i) => (
|
||||||
<section key={i}>
|
<section key={i}>
|
||||||
<h3 id={`doc-${i}`}>{entry.name}</h3>
|
<h3 id={`doc-${i}`}>{entry.name}</h3>
|
||||||
|
{!!entry.synonyms_text && (
|
||||||
|
<p>
|
||||||
|
Synonyms: <code>{entry.synonyms_text}</code>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{/* <small>{entry.meta.filename}</small> */}
|
{/* <small>{entry.meta.filename}</small> */}
|
||||||
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
|
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@ -3,6 +3,9 @@ import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycle
|
|||||||
import './piano.mjs';
|
import './piano.mjs';
|
||||||
import './files.mjs';
|
import './files.mjs';
|
||||||
|
|
||||||
|
const { BASE_URL } = import.meta.env;
|
||||||
|
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||||
|
|
||||||
export async function prebake() {
|
export async function prebake() {
|
||||||
// https://archive.org/details/SalamanderGrandPianoV3
|
// https://archive.org/details/SalamanderGrandPianoV3
|
||||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||||
@ -14,16 +17,16 @@ export async function prebake() {
|
|||||||
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
|
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
|
||||||
// seems to be a problem with soundfont2
|
// seems to be a problem with soundfont2
|
||||||
import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()),
|
import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()),
|
||||||
samples(`./piano.json`, `./piano/`, { prebake: true }),
|
samples(`${baseNoTrailing}/piano.json`, `${baseNoTrailing}/piano/`, { prebake: true }),
|
||||||
// https://github.com/sgossner/VCSL/
|
// https://github.com/sgossner/VCSL/
|
||||||
// https://api.github.com/repositories/126427031/contents/
|
// https://api.github.com/repositories/126427031/contents/
|
||||||
// LICENSE: CC0 general-purpose
|
// LICENSE: CC0 general-purpose
|
||||||
samples(`./vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
|
samples(`${baseNoTrailing}/vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
|
||||||
samples(`./tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
|
samples(`${baseNoTrailing}/tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
|
||||||
prebake: true,
|
prebake: true,
|
||||||
tag: 'drum-machines',
|
tag: 'drum-machines',
|
||||||
}),
|
}),
|
||||||
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, tag: 'drum-machines' }),
|
samples(`${baseNoTrailing}/EmuSP12.json`, `${baseNoTrailing}/EmuSP12/`, { prebake: true, tag: 'drum-machines' }),
|
||||||
samples(
|
samples(
|
||||||
{
|
{
|
||||||
casio: ['casio/high.wav', 'casio/low.wav', 'casio/noise.wav'],
|
casio: ['casio/high.wav', 'casio/low.wav', 'casio/noise.wav'],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user