mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-21 20:55:12 -04:00
Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc335c9930 | |||
| 0d67176a98 | |||
| 77bbb9626a | |||
| 65cc317382 | |||
| 36d4d89052 | |||
| f8a241401c | |||
| 1baa6da370 | |||
| 9e0b840afd | |||
| e6fbef95f2 | |||
| d9bd6f9e7d | |||
| 1e0cc2ab17 | |||
| cad11e1e48 | |||
| b85a5a9179 | |||
| 9b4675f1ce | |||
| 67c0ebaf54 | |||
| 8d1180f87a | |||
| fcd550b5bd | |||
| d3e624f673 | |||
| b0149f8190 | |||
| bf3a095852 | |||
| 5c983ceb8f | |||
| 735d531cce | |||
| 835c7b6879 | |||
| 13168bb9bc | |||
| 46b4aff0ef | |||
| 721e7f06b8 | |||
| a46bbeaa10 | |||
| e3a3c0c5f9 | |||
| 67964c7e23 | |||
| 1b8998ac14 | |||
| 9e5f9d37ef | |||
| 0c1234a35f | |||
| c6cc487fc6 | |||
| f0ddb2d595 | |||
| dd171f8320 | |||
| c22c786cf2 | |||
| c191471e1e | |||
| 0d0cb3fd93 | |||
| bb4b7dc533 | |||
| 8440fa42ea | |||
| 61f070744a | |||
| 2e6a561184 | |||
| 8703d684c4 | |||
| b0d2115f43 | |||
| 96802a271c | |||
| a91f4576f3 | |||
| ad080d02b3 | |||
| e9e7e02547 | |||
| 2ffc6c9601 | |||
| 3a9349d9f5 | |||
| bd3f2845f8 | |||
| 2d320c459b | |||
| ff9f4a8a0d | |||
| d7b94807cb | |||
| 2c16a37a68 | |||
| 717e4a2c1d | |||
| c225b4719f | |||
| 25f762c393 | |||
| b75561e3c7 | |||
| abaab89061 | |||
| e409086b34 | |||
| 532f1435d9 | |||
| 6cb61ade03 | |||
| a40219ba06 | |||
| 9c99f5ada2 | |||
| 2d72c8faa0 | |||
| b188fc6b6d | |||
| bfb22228cc | |||
| 9714d88d61 | |||
| bdddb91510 | |||
| dffeac41af | |||
| 943d78f268 | |||
| 5ac59e95dd | |||
| 69a5d62027 | |||
| 226cf356d6 | |||
| 73d49f152a | |||
| cdba9efb59 |
@@ -23,3 +23,4 @@ reverbGen.mjs
|
||||
hydra.mjs
|
||||
jsdoc-synonyms.js
|
||||
packages/hs2js/src/hs2js.mjs
|
||||
samples
|
||||
@@ -29,6 +29,7 @@ const editor = new StrudelMirror({
|
||||
import('@strudel/core'),
|
||||
import('@strudel/draw'),
|
||||
import('@strudel/mini'),
|
||||
import('@strudel/shader'),
|
||||
import('@strudel/tonal'),
|
||||
import('@strudel/webaudio'),
|
||||
);
|
||||
|
||||
+7
-7
@@ -56,18 +56,18 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@vitest/ui": "^1.1.0",
|
||||
"acorn": "^8.11.3",
|
||||
"@vitest/ui": "^2.1.3",
|
||||
"acorn": "^8.13.0",
|
||||
"dependency-tree": "^10.0.9",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"events": "^3.3.0",
|
||||
"jsdoc": "^4.0.2",
|
||||
"jsdoc": "^4.0.3",
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"jsdoc-to-markdown": "^8.0.0",
|
||||
"lerna": "^8.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"lerna": "^8.1.8",
|
||||
"prettier": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^2.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +99,8 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
||||
},
|
||||
{
|
||||
key: 'Alt-.',
|
||||
run: (_, e) => {
|
||||
e.preventDefault();
|
||||
onStop?.();
|
||||
},
|
||||
preventDefault: true,
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
/* {
|
||||
key: 'Ctrl-Shift-.',
|
||||
|
||||
@@ -73,49 +73,6 @@ export function registerControl(names, ...aliases) {
|
||||
*/
|
||||
export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound');
|
||||
|
||||
/**
|
||||
* generic macro param for a sound synth
|
||||
*
|
||||
* @name z1
|
||||
* @synonyms z
|
||||
* @param {number | Pattern}
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("supersaw").z(".01 .75").release(0.5)
|
||||
*
|
||||
*/
|
||||
export const { z1, z } = registerControl(['z1', 'z2', 'z3', 'z4'], 'z');
|
||||
|
||||
/**
|
||||
* generic macro param for a sound synth
|
||||
*
|
||||
* @name z2
|
||||
* @param {number | Pattern}
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("supersaw").z2("2 .7").release(0.5)
|
||||
*
|
||||
*/
|
||||
export const { z2 } = registerControl('z2');
|
||||
|
||||
/**
|
||||
* generic macro param for a sound synth
|
||||
*
|
||||
* @name z3
|
||||
* @param {number | Pattern}
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("supersaw").z3(".1 .75").release(0.5)
|
||||
*
|
||||
*/
|
||||
export const { z3 } = registerControl('z3');
|
||||
|
||||
/**
|
||||
* generic macro param for a sound synth
|
||||
*
|
||||
* @name z4
|
||||
* @param {number | Pattern}
|
||||
*
|
||||
*/
|
||||
export const { z4 } = registerControl('z4');
|
||||
|
||||
/**
|
||||
* Define a custom webaudio node to use as a sound source.
|
||||
*
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^2.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
+107
-31
@@ -809,15 +809,6 @@ export class Pattern {
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Multi-pattern functions
|
||||
|
||||
/**
|
||||
* Stacks the given pattern(s) to the current pattern.
|
||||
* @name stack
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* s("hh*4").stack(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
stack(...pats) {
|
||||
return stack(this, ...pats);
|
||||
}
|
||||
@@ -826,30 +817,9 @@ export class Pattern {
|
||||
return sequence(this, ...pats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given pattern(s) to the current pattern.
|
||||
* @name seq
|
||||
* @memberof Pattern
|
||||
* @synonyms sequence, fastcat
|
||||
* @example
|
||||
* s("hh*4").seq(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
seq(...pats) {
|
||||
return sequence(this, ...pats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given pattern(s) to the next cycle.
|
||||
* @name cat
|
||||
* @memberof Pattern
|
||||
* @synonyms slowcat
|
||||
* @example
|
||||
* s("hh*4").cat(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
cat(...pats) {
|
||||
return cat(this, ...pats);
|
||||
}
|
||||
@@ -1280,6 +1250,12 @@ export function reify(thing) {
|
||||
* @example
|
||||
* stack("g3", "b3", ["e4", "d4"]).note()
|
||||
* // "g3,b3,[e4,d4]".note()
|
||||
*
|
||||
* @example
|
||||
* // As a chained function:
|
||||
* s("hh*4").stack(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
export function stack(...pats) {
|
||||
// Array test here is to avoid infinite recursions..
|
||||
@@ -1408,6 +1384,11 @@ export function slowcatPrime(...pats) {
|
||||
* cat("e5", "b4", ["d5", "c5"]).note()
|
||||
* // "<e5 b4 [d5 c5]>".note()
|
||||
*
|
||||
* @example
|
||||
* // As a chained function:
|
||||
* s("hh*4").cat(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
export function cat(...pats) {
|
||||
return slowcat(...pats);
|
||||
@@ -1430,6 +1411,36 @@ export function arrange(...sections) {
|
||||
return s_cat(...sections).slow(total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similarly to `arrange`, allows you to arrange multiple patterns together over multiple cycles.
|
||||
* Unlike `arrange`, you specify a start and stop time for each pattern rather than duration, which
|
||||
* means that patterns can overlap.
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
seqPLoop([0, 2, "bd(3,8)"],
|
||||
[1, 3, "cp(3,8)"]
|
||||
)
|
||||
.sound()
|
||||
*/
|
||||
export function seqPLoop(...parts) {
|
||||
let total = Fraction(0);
|
||||
const pats = [];
|
||||
for (let part of parts) {
|
||||
if (part.length == 2) {
|
||||
part.unshift(total);
|
||||
}
|
||||
total = part[1];
|
||||
}
|
||||
|
||||
return stack(
|
||||
...parts.map(([start, stop, pat]) =>
|
||||
pure(reify(pat)).compress(Fraction(start).div(total), Fraction(stop).div(total)),
|
||||
),
|
||||
)
|
||||
.slow(total)
|
||||
.innerJoin(); // or resetJoin or restartJoin ??
|
||||
}
|
||||
|
||||
export function fastcat(...pats) {
|
||||
let result = slowcat(...pats);
|
||||
if (pats.length > 1) {
|
||||
@@ -1448,12 +1459,18 @@ export function sequence(...pats) {
|
||||
}
|
||||
|
||||
/** Like **cat**, but the items are crammed into one cycle.
|
||||
* @synonyms fastcat, sequence
|
||||
* @synonyms sequence, fastcat
|
||||
* @example
|
||||
* seq("e5", "b4", ["d5", "c5"]).note()
|
||||
* // "e5 b4 [d5 c5]".note()
|
||||
*
|
||||
* @example
|
||||
* // As a chained function:
|
||||
* s("hh*4").seq(
|
||||
* note("c4(5,8)")
|
||||
* )
|
||||
*/
|
||||
|
||||
export function seq(...pats) {
|
||||
return fastcat(...pats);
|
||||
}
|
||||
@@ -2016,6 +2033,34 @@ export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a
|
||||
return pat.zoom(a.begin, a.end);
|
||||
});
|
||||
|
||||
/**
|
||||
* Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers.
|
||||
* Similar to `slice`, but slices up patterns rather than sound samples.
|
||||
* @param {number} number of slices
|
||||
* @param {number} slices to play
|
||||
* @example
|
||||
* note("0 1 2 3 4 5 6 7".scale('c:mixolydian'))
|
||||
*.bite(4, "3 2 1 0")
|
||||
* @example
|
||||
* sound("bd - bd bd*2, - sd:6 - sd:5 sd:1 - [- sd:2] -, hh [- cp:7]")
|
||||
.bank("RolandTR909").speed(1.2)
|
||||
.bite(4, "0 0 [1 2] <3 2> 0 0 [2 1] 3")
|
||||
*/
|
||||
export const bite = register(
|
||||
'bite',
|
||||
(npat, ipat, pat) => {
|
||||
return ipat
|
||||
.fmap((i) => (n) => {
|
||||
const a = Fraction(i).div(n).mod(1);
|
||||
const b = a.add(Fraction(1).div(n));
|
||||
return pat.zoom(a, b);
|
||||
})
|
||||
.appLeft(npat)
|
||||
.squeezeJoin();
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle.
|
||||
* @param {number} fraction fraction to select
|
||||
@@ -2447,6 +2492,37 @@ Pattern.prototype.tag = function (tag) {
|
||||
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters haps using the given function
|
||||
* @name filter
|
||||
* @param {Function} test function to test Hap
|
||||
* @example
|
||||
* s("hh!7 oh").filter(hap => hap.value.s==='hh')
|
||||
*/
|
||||
export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test)));
|
||||
|
||||
/**
|
||||
* Filters haps by their begin time
|
||||
* @name filterWhen
|
||||
* @noAutocomplete
|
||||
* @param {Function} test function to test Hap.whole.begin
|
||||
*/
|
||||
export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin)));
|
||||
|
||||
/**
|
||||
* Use within to apply a function to only a part of a pattern.
|
||||
* @name within
|
||||
* @param {number} start start within cycle (0 - 1)
|
||||
* @param {number} end end within cycle (0 - 1). Must be > start
|
||||
* @param {Function} func function to be applied to the sub-pattern
|
||||
*/
|
||||
export const within = register('within', (a, b, fn, pat) =>
|
||||
stack(
|
||||
fn(pat.filterWhen((t) => t.cyclePos() >= a && t.cyclePos() <= b)),
|
||||
pat.filterWhen((t) => t.cyclePos() < a || t.cyclePos() > b),
|
||||
),
|
||||
);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Tactus-related functions, i.e. ones that do stepwise
|
||||
// transformations
|
||||
|
||||
@@ -169,13 +169,18 @@ const refs = {};
|
||||
export async function midin(input) {
|
||||
if (isPattern(input)) {
|
||||
throw new Error(
|
||||
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
|
||||
`midin: does not accept Pattern as input. Make sure to pass device name with single quotes. Example: midin('${
|
||||
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
|
||||
}')`,
|
||||
);
|
||||
}
|
||||
const initial = await enableWebMidi(); // only returns on first init
|
||||
const device = getDevice(input, WebMidi.inputs);
|
||||
if (!device) {
|
||||
throw new Error(
|
||||
`midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
|
||||
);
|
||||
}
|
||||
if (initial) {
|
||||
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
|
||||
logger(
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
"devDependencies": {
|
||||
"peggy": "^3.0.2",
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^2.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export async function prebake() {
|
||||
import('@strudel/webaudio'),
|
||||
import('@strudel/codemirror'),
|
||||
import('@strudel/hydra'),
|
||||
import('@strudel/shader'),
|
||||
import('@strudel/soundfonts'),
|
||||
import('@strudel/midi'),
|
||||
// import('@strudel/xen'),
|
||||
|
||||
@@ -68,7 +68,7 @@ Pattern.prototype.serial = function (br = 115200, sendcrc = false, singlecharids
|
||||
if (!(name in writeMessagers)) {
|
||||
getWriter(name, br);
|
||||
}
|
||||
const onTrigger = (time, hap, currentTime) => {
|
||||
const onTrigger = (t_deprecate, hap, currentTime, cps, targetTime) => {
|
||||
var message = '';
|
||||
var chk = 0;
|
||||
if (typeof hap.value === 'object') {
|
||||
@@ -105,7 +105,7 @@ Pattern.prototype.serial = function (br = 115200, sendcrc = false, singlecharids
|
||||
} else {
|
||||
message = hap.value;
|
||||
}
|
||||
const offset = (time - currentTime + latency) * 1000;
|
||||
const offset = (targetTime - currentTime + latency) * 1000;
|
||||
|
||||
window.setTimeout(function () {
|
||||
writeMessagers[name](message, chk);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# @strudel/shader
|
||||
|
||||
Helpers for drawing shader.
|
||||
|
||||
## Todos
|
||||
|
||||
Here are the things that needs to be implemented:
|
||||
|
||||
- [ ] Shader compilation error reporting, e.g. to show the line number
|
||||
- [ ] Shader import from url, like shadertoy or git
|
||||
- [ ] Display shader author attribution, e.g. to respect CC-BY
|
||||
- [ ] Handle WebGL context lost by restoring the objects.
|
||||
- [ ] Multiple instances and custom canvas positions
|
||||
- [ ] Multiple programs, to be swapped like a pattern
|
||||
- [ ] Texture inputs
|
||||
- [ ] Buffer inputs, e.g. to generate a texture
|
||||
@@ -0,0 +1 @@
|
||||
export * from './shader.mjs';
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@strudel/shader",
|
||||
"version": "1.0.0",
|
||||
"description": "Helpers for drawing shader",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"titdalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave",
|
||||
"shader"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel/core": "workspace:*",
|
||||
"picogl": "^0.17.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^2.1.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
shader.mjs - implements the `loadShader` function
|
||||
Copyright (C) 2024 Strudel 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
/// Here is a feature demo
|
||||
// Setup a shader
|
||||
let truchetFTW = fetch('https://raw.githubusercontent.com/TristanCacqueray/shaders/refs/heads/main/shaders/Truchet%20%2B%20Kaleidoscope%20FTW.glsl').then((res) => res.text())
|
||||
// This shader provides the following uniforms:
|
||||
// uniform float icolor;
|
||||
// uniform float moveFWD;
|
||||
// uniform float rotations[4];
|
||||
// uniform float modulations[6];
|
||||
|
||||
// Start the instance and binds the uniforms
|
||||
let {uniforms} = await loadShader(truchetFTW)
|
||||
|
||||
setcpm(96)
|
||||
|
||||
// A smoothing function that is called for each frame
|
||||
let smooth = (desired, speed) => (value) => value + ((desired - value) / speed)
|
||||
|
||||
// Each kick updates a different rotation value.
|
||||
let rotationIndex = 0
|
||||
$: s("bd").bank("RolandTR808")
|
||||
.gain(2).dist("<1 .7 .7 .7>")
|
||||
.mask("<1@30 0@2>")
|
||||
.onTrigger(() => uniforms.rotations.set(
|
||||
cur => smooth(cur + 1, 20), rotationIndex++),
|
||||
false)
|
||||
|
||||
// Each hat increase the icolor value.
|
||||
$: sound("hh*4").bank("RolandTR808")
|
||||
.room(.3).gain(".25 .3 .4")
|
||||
.mask("<0@8 1@32>")
|
||||
.onTrigger(() => uniforms.icolor.incr(0.1), false)
|
||||
|
||||
// The snare smoothly increase the moveFWD value
|
||||
$: s("cp/8").bank("RolandTR808")
|
||||
.hpf(500).hpa(.8).hpenv("<-3 -2 -3 -2 -1>/8")
|
||||
.room(0.5).roomsize(7).rlp(5000).gain(.2)
|
||||
.onTrigger(() => uniforms.moveFWD.set(cur => smooth(cur + 1, 30)), false)
|
||||
|
||||
// Each piano note updates a different modulations value
|
||||
let pianoPitches = {}
|
||||
$: note("<C D G A Bb D C A G D Bb A>*[2,2.02]")
|
||||
.clip(1.1)
|
||||
.transpose("<-12 -24 -12 0>/8")
|
||||
// .sound("sawtooth")
|
||||
.sound("triangle")
|
||||
.cutoff(perlin.slow(5).range(20,1200))
|
||||
.room(.8).roomsize(.6)
|
||||
.gain(.4)
|
||||
.onTrigger((_, hap) => {
|
||||
const n = hap.value.note
|
||||
// assign unique array position for each new notes
|
||||
if (!pianoPitches[n]) pianoPitches[n] = Object.keys(pianoPitches).length + 1
|
||||
const idx = pianoPitches[n]
|
||||
uniforms.modulations.set(cur => smooth(cur + .5, 55), idx)
|
||||
}, false)
|
||||
*/
|
||||
|
||||
import { logger } from '@strudel/core';
|
||||
|
||||
// The standard fullscreen vertex shader.
|
||||
const vertexShader = `#version 300 es
|
||||
precision highp float;
|
||||
layout(location=0) in vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 1, 1);
|
||||
}
|
||||
`;
|
||||
|
||||
// Make the fragment source, similar to the one from shadertoy.
|
||||
function mkFragmentShader(code) {
|
||||
return `#version 300 es
|
||||
precision highp float;
|
||||
out vec4 oColor;
|
||||
uniform float iTime;
|
||||
uniform vec2 iResolution;
|
||||
|
||||
#define STRUDEL 1
|
||||
|
||||
${code}
|
||||
|
||||
void main(void) {
|
||||
mainImage(oColor, gl_FragCoord.xy);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
// Helper class to handle uniform updates
|
||||
class UniformValue {
|
||||
constructor(count, draw) {
|
||||
this.draw = draw;
|
||||
this.value = new Array(count).fill(0);
|
||||
this.frameModifier = new Array(count).fill(null);
|
||||
}
|
||||
|
||||
// Helper to perform a simple increment
|
||||
incr(value, pos = 0) {
|
||||
const idx = pos % this.value.length;
|
||||
this.value[idx] += value;
|
||||
this.frameModifier[idx] = null;
|
||||
this.draw();
|
||||
}
|
||||
|
||||
// The value can be a function that will be called for each rendering frame
|
||||
set(value, pos = 0) {
|
||||
const idx = pos % this.value.length;
|
||||
if (typeof value === 'function') {
|
||||
this.frameModifier[idx] = value(this.value[idx]);
|
||||
} else {
|
||||
this.value[idx] = value;
|
||||
this.frameModifier[idx] = null;
|
||||
}
|
||||
this.draw();
|
||||
}
|
||||
|
||||
get(pos = 0) {
|
||||
return this.value[pos % this.value.length];
|
||||
}
|
||||
|
||||
// This function is called for every frame, allowing to run a smooth modifier
|
||||
_frameUpdate(elapsed) {
|
||||
this.value = this.value.map((value, idx) =>
|
||||
this.frameModifier[idx] ? this.frameModifier[idx](value, elapsed) : value,
|
||||
);
|
||||
return this.value;
|
||||
}
|
||||
|
||||
// When the shader is update, this function adjust the number of values, preserving the current one
|
||||
_resize(count) {
|
||||
if (count != this.count) {
|
||||
count = Math.max(1, count);
|
||||
resizeArray(this.value, count, 0);
|
||||
resizeArray(this.frameModifier, count, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink or extend an array
|
||||
function resizeArray(arr, size, defval) {
|
||||
if (arr.length > size) arr.length = size;
|
||||
else arr.push(...new Array(size - arr.length).fill(defval));
|
||||
}
|
||||
|
||||
// Setup the instance's uniform after shader compilation.
|
||||
function setupUniforms(instance) {
|
||||
const newUniforms = new Set();
|
||||
const draw = () => {
|
||||
// Start the drawing loop
|
||||
instance.age = 0;
|
||||
if (!instance.drawing) {
|
||||
instance.drawing = requestAnimationFrame(instance.update);
|
||||
}
|
||||
};
|
||||
|
||||
// Collect every available uniforms
|
||||
let gl = instance.gl;
|
||||
const numUniforms = instance.gl.getProgramParameter(instance.program, gl.ACTIVE_UNIFORMS);
|
||||
for (let i = 0; i < numUniforms; ++i) {
|
||||
const inf = gl.getActiveUniform(instance.program, i);
|
||||
|
||||
// Arrays have a `[0]` suffix in their name, drop that
|
||||
const name = inf.name.replace('[0]', '');
|
||||
|
||||
// Figure out how many values is this uniform, and how to update it.
|
||||
let count = inf.size;
|
||||
let updateFunc = 'uniform1fv';
|
||||
switch (inf.type) {
|
||||
case gl.FLOAT_VEC2:
|
||||
count *= 2;
|
||||
updateFunc = 'uniform2fv';
|
||||
break;
|
||||
case gl.FLOAT_VEC3:
|
||||
count *= 3;
|
||||
updateFunc = 'uniform3fv';
|
||||
break;
|
||||
case gl.FLOAT_VEC4:
|
||||
count *= 4;
|
||||
updateFunc = 'uniform4fv';
|
||||
break;
|
||||
}
|
||||
|
||||
// This is a new uniform
|
||||
if (!instance.uniforms[name]) instance.uniforms[name] = new UniformValue(count, draw);
|
||||
// This is a known uniform, make sure it's size is correct
|
||||
else instance.uniforms[name]._resize(count);
|
||||
|
||||
// Record it's location for the 'updateUniforms' below.
|
||||
instance.uniforms[name].loc = gl.getUniformLocation(instance.program, inf.name);
|
||||
instance.uniforms[name].updateFunc = updateFunc;
|
||||
|
||||
// Record the name so that unused uniform can be deleted below
|
||||
newUniforms.add(name);
|
||||
}
|
||||
|
||||
// Remove deleted uniforms
|
||||
Object.keys(instance.uniforms).forEach((name) => {
|
||||
if (!newUniforms.has(name)) delete instance.uniforms[name];
|
||||
});
|
||||
}
|
||||
|
||||
// Update the uniforms for a given drawFrame call.
|
||||
function updateUniforms(gl, now, elapsed, uniforms) {
|
||||
Object.entries(uniforms).forEach(([name, uniform]) => {
|
||||
try {
|
||||
if (name == 'iTime') {
|
||||
gl.uniform1f(uniform.loc, now);
|
||||
} else if (name == 'iResolution') {
|
||||
gl.uniform2f(uniform.loc, gl.canvas.width, gl.canvas.height);
|
||||
} else {
|
||||
const value = uniform._frameUpdate(elapsed);
|
||||
// Send the value to the GPU
|
||||
// console.log('updateUniforms:', name, uniform.updateFunc, value);
|
||||
gl[uniform.updateFunc](uniform.loc, value);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('uniform error');
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Setup the canvas and return the WebGL context.
|
||||
function setupCanvas(name) {
|
||||
// TODO: support custom size
|
||||
const width = 400;
|
||||
const height = 300;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.id = 'cnv-' + name;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const top = 60 + Object.keys(_instances).length * height;
|
||||
canvas.style = `pointer-events:none;width:${width}px;height:${height}px;position:fixed;top:${top}px;right:23px`;
|
||||
document.body.append(canvas);
|
||||
return canvas.getContext('webgl2');
|
||||
}
|
||||
|
||||
function createProgram(gl, vertex, fragment) {
|
||||
const compile = (type, source) => {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
||||
if (!success) {
|
||||
const err = gl.getShaderInfoLog(shader);
|
||||
gl.deleteShader(shader);
|
||||
throw err;
|
||||
}
|
||||
return shader;
|
||||
};
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, compile(gl.VERTEX_SHADER, vertex));
|
||||
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fragment));
|
||||
gl.linkProgram(program);
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
const err = gl.getProgramInfoLog(program);
|
||||
gl.deleteProgram(program);
|
||||
throw err;
|
||||
}
|
||||
gl.useProgram(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
// Setup the shader instance
|
||||
function initializeShaderInstance(name, code) {
|
||||
const gl = setupCanvas(name);
|
||||
|
||||
// Two triangle to cover the whole canvas
|
||||
const mkPositionArray = () => {
|
||||
const buf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
|
||||
const vao = gl.createVertexArray();
|
||||
gl.bindVertexArray(vao);
|
||||
gl.enableVertexAttribArray(0);
|
||||
gl.vertexAttribPointer(buf, 2, gl.FLOAT, false, 0, 0);
|
||||
return vao;
|
||||
};
|
||||
|
||||
try {
|
||||
let array = mkPositionArray();
|
||||
let program = createProgram(gl, vertexShader, code);
|
||||
const instance = { gl, code, program, array, uniforms: {} };
|
||||
setupUniforms(instance);
|
||||
// Render frame logic
|
||||
let prev = performance.now() / 1000;
|
||||
instance.age = 0;
|
||||
instance.update = () => {
|
||||
const now = performance.now() / 1000;
|
||||
const elapsed = instance.age == 0 ? 1 / 60 : now - prev;
|
||||
prev = now;
|
||||
// console.log('drawing!');
|
||||
|
||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||
|
||||
// Clear the canvas
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.bindVertexArray(array);
|
||||
|
||||
// Send the uniform values to the GPU
|
||||
updateUniforms(instance.gl, now, elapsed, instance.uniforms);
|
||||
|
||||
// Draw the quad
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
// After sometime, if no update happened, stop the animation loop to save cpu cycles
|
||||
if (instance.age++ < 100) requestAnimationFrame(instance.update);
|
||||
else instance.drawing = false;
|
||||
};
|
||||
instance.update();
|
||||
return instance;
|
||||
} catch (err) {
|
||||
gl.canvas.remove();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the instance program
|
||||
function reloadShaderInstanceCode(instance, code) {
|
||||
const program = createProgram(instance.gl, vertexShader, code);
|
||||
instance.gl.deleteProgram(instance.program);
|
||||
instance.program = program;
|
||||
instance.code = code;
|
||||
setupUniforms(instance);
|
||||
}
|
||||
|
||||
// Keep track of the running shader instances
|
||||
let _instances = {};
|
||||
export function loadShader(code = '', name = 'default') {
|
||||
if (code) {
|
||||
code = mkFragmentShader(code);
|
||||
}
|
||||
if (!_instances[name]) {
|
||||
_instances[name] = initializeShaderInstance(name, code);
|
||||
logger('[shader] ready');
|
||||
} else if (_instances[name].code != code) {
|
||||
reloadShaderInstanceCode(_instances[name], code);
|
||||
logger('[shader] reloaded');
|
||||
}
|
||||
return _instances[name];
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
},
|
||||
target: 'esnext',
|
||||
},
|
||||
});
|
||||
@@ -22,16 +22,13 @@ function humanFileSize(bytes, si) {
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
|
||||
export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resolveUrl) => {
|
||||
let transpose = 0;
|
||||
if (freq !== undefined && note !== undefined) {
|
||||
logger('[sampler] hap has note and freq. ignoring note', 'warning');
|
||||
}
|
||||
let midi = valueToMidi({ freq, note }, 36);
|
||||
transpose = midi - 36; // C3 is middle C
|
||||
|
||||
const ac = getAudioContext();
|
||||
|
||||
// deduces relevant info for sample loading from hap.value and sample definition
|
||||
// it encapsulates the core sampler logic into a pure and synchronous function
|
||||
// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format)
|
||||
export function getSampleInfo(hapValue, bank) {
|
||||
const { s, n = 0, speed = 1.0 } = hapValue;
|
||||
let midi = valueToMidi(hapValue, 36);
|
||||
let transpose = midi - 36; // C3 is middle C;
|
||||
let sampleUrl;
|
||||
let index = 0;
|
||||
if (Array.isArray(bank)) {
|
||||
@@ -50,19 +47,54 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resol
|
||||
index = getSoundIndex(n, bank[closest].length);
|
||||
sampleUrl = bank[closest][index];
|
||||
}
|
||||
const label = `${s}:${index}`;
|
||||
let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12);
|
||||
return { transpose, sampleUrl, index, midi, label, playbackRate };
|
||||
}
|
||||
|
||||
// takes hapValue and returns buffer + playbackRate.
|
||||
export const getSampleBuffer = async (hapValue, bank, resolveUrl) => {
|
||||
let { sampleUrl, label, playbackRate } = getSampleInfo(hapValue, bank);
|
||||
if (resolveUrl) {
|
||||
sampleUrl = await resolveUrl(sampleUrl);
|
||||
}
|
||||
let buffer = await loadBuffer(sampleUrl, ac, s, index);
|
||||
if (speed < 0) {
|
||||
const ac = getAudioContext();
|
||||
const buffer = await loadBuffer(sampleUrl, ac, label);
|
||||
|
||||
if (hapValue.unit === 'c') {
|
||||
playbackRate = playbackRate * buffer.duration;
|
||||
}
|
||||
return { buffer, playbackRate };
|
||||
};
|
||||
|
||||
// creates playback ready AudioBufferSourceNode from hapValue
|
||||
export const getSampleBufferSource = async (hapValue, bank, resolveUrl) => {
|
||||
let { buffer, playbackRate } = await getSampleBuffer(hapValue, bank, resolveUrl);
|
||||
if (hapValue.speed < 0) {
|
||||
// should this be cached?
|
||||
buffer = reverseBuffer(buffer);
|
||||
}
|
||||
const ac = getAudioContext();
|
||||
const bufferSource = ac.createBufferSource();
|
||||
bufferSource.buffer = buffer;
|
||||
const playbackRate = 1.0 * Math.pow(2, transpose / 12);
|
||||
bufferSource.playbackRate.value = playbackRate;
|
||||
return bufferSource;
|
||||
|
||||
const { s, loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue;
|
||||
|
||||
// "The computation of the offset into the sound is performed using the sound buffer's natural sample rate,
|
||||
// rather than the current playback rate, so even if the sound is playing at twice its normal speed,
|
||||
// the midway point through a 10-second audio buffer is still 5."
|
||||
const offset = begin * bufferSource.buffer.duration;
|
||||
|
||||
const loop = s.startsWith('wt_') ? 1 : hapValue.loop;
|
||||
if (loop) {
|
||||
bufferSource.loop = true;
|
||||
bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset;
|
||||
bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset;
|
||||
}
|
||||
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
||||
const sliceDuration = (end - begin) * bufferDuration;
|
||||
return { bufferSource, offset, bufferDuration, sliceDuration };
|
||||
};
|
||||
|
||||
export const loadBuffer = (url, ac, s, n = 0) => {
|
||||
@@ -232,10 +264,10 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
|
||||
const { prebake, tag } = options;
|
||||
processSampleMap(
|
||||
sampleMap,
|
||||
(key, value) =>
|
||||
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
|
||||
(key, bank) =>
|
||||
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), {
|
||||
type: 'sample',
|
||||
samples: value,
|
||||
samples: bank,
|
||||
baseUrl,
|
||||
prebake,
|
||||
tag,
|
||||
@@ -249,38 +281,26 @@ const cutGroups = [];
|
||||
export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||
let {
|
||||
s,
|
||||
freq,
|
||||
unit,
|
||||
nudge = 0, // TODO: is this in seconds?
|
||||
cut,
|
||||
loop,
|
||||
clip = undefined, // if set, samples will be cut off when the hap ends
|
||||
n = 0,
|
||||
note,
|
||||
speed = 1, // sample playback speed
|
||||
loopBegin = 0,
|
||||
begin = 0,
|
||||
loopEnd = 1,
|
||||
end = 1,
|
||||
duration,
|
||||
} = value;
|
||||
|
||||
// load sample
|
||||
if (speed === 0) {
|
||||
// no playback
|
||||
return;
|
||||
}
|
||||
loop = s.startsWith('wt_') ? 1 : value.loop;
|
||||
const ac = getAudioContext();
|
||||
|
||||
// destructure adsr here, because the default should be different for synths and samples
|
||||
|
||||
let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
|
||||
//const soundfont = getSoundfontKey(s);
|
||||
const time = t + nudge;
|
||||
|
||||
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
|
||||
|
||||
// vibrato
|
||||
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
|
||||
const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl);
|
||||
|
||||
// asny stuff above took too long?
|
||||
if (ac.currentTime > t) {
|
||||
@@ -292,26 +312,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||
logger(`[sampler] could not load "${s}:${n}"`, 'error');
|
||||
return;
|
||||
}
|
||||
bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value;
|
||||
if (unit === 'c') {
|
||||
// are there other units?
|
||||
bufferSource.playbackRate.value = bufferSource.playbackRate.value * bufferSource.buffer.duration * 1; //cps;
|
||||
}
|
||||
// "The computation of the offset into the sound is performed using the sound buffer's natural sample rate,
|
||||
// rather than the current playback rate, so even if the sound is playing at twice its normal speed,
|
||||
// the midway point through a 10-second audio buffer is still 5."
|
||||
const offset = begin * bufferSource.buffer.duration;
|
||||
if (loop) {
|
||||
bufferSource.loop = true;
|
||||
bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset;
|
||||
bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset;
|
||||
}
|
||||
|
||||
// vibrato
|
||||
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
|
||||
|
||||
const time = t + nudge;
|
||||
bufferSource.start(time, offset);
|
||||
|
||||
const envGain = ac.createGain();
|
||||
const node = bufferSource.connect(envGain);
|
||||
|
||||
// if none of these controls is set, the duration of the sound will be set to the duration of the sample slice
|
||||
if (clip == null && loop == null && value.release == null) {
|
||||
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
||||
duration = (end - begin) * bufferDuration;
|
||||
duration = sliceDuration;
|
||||
}
|
||||
let holdEnd = t + duration;
|
||||
|
||||
|
||||
@@ -421,6 +421,7 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
};
|
||||
if (bank && s) {
|
||||
s = `${bank}_${s}`;
|
||||
value.s = s;
|
||||
}
|
||||
|
||||
// get source AudioNode
|
||||
|
||||
@@ -73,9 +73,8 @@ export function registerSynthSounds() {
|
||||
'supersaw',
|
||||
(begin, value, onended) => {
|
||||
const ac = getAudioContext();
|
||||
const { z1, z2, z3 } = value;
|
||||
let { duration, n, unison = z2 == null ? 5 : Math.round(z2 * 10), spread = z3 ?? 0.6, detune } = value;
|
||||
detune = detune ?? n ?? z1 ?? 0.18;
|
||||
let { duration, n, unison = 5, spread = 0.6, detune } = value;
|
||||
detune = detune ?? n ?? 0.18;
|
||||
const frequency = getFrequencyFromValue(value);
|
||||
|
||||
const [attack, decay, sustain, release] = getADSRValues(
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^2.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^2.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^1.1.0"
|
||||
"vitest": "^2.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+11469
-9230
File diff suppressed because it is too large
Load Diff
@@ -967,6 +967,156 @@ exports[`runs examples > example "begin" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "bite" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:Bb3 ]",
|
||||
"[ 1/8 → 1/4 | note:C4 ]",
|
||||
"[ 1/4 → 3/8 | note:G3 ]",
|
||||
"[ 3/8 → 1/2 | note:A3 ]",
|
||||
"[ 1/2 → 5/8 | note:E3 ]",
|
||||
"[ 5/8 → 3/4 | note:F3 ]",
|
||||
"[ 3/4 → 7/8 | note:C3 ]",
|
||||
"[ 7/8 → 1/1 | note:D3 ]",
|
||||
"[ 1/1 → 9/8 | note:Bb3 ]",
|
||||
"[ 9/8 → 5/4 | note:C4 ]",
|
||||
"[ 5/4 → 11/8 | note:G3 ]",
|
||||
"[ 11/8 → 3/2 | note:A3 ]",
|
||||
"[ 3/2 → 13/8 | note:E3 ]",
|
||||
"[ 13/8 → 7/4 | note:F3 ]",
|
||||
"[ 7/4 → 15/8 | note:C3 ]",
|
||||
"[ 15/8 → 2/1 | note:D3 ]",
|
||||
"[ 2/1 → 17/8 | note:Bb3 ]",
|
||||
"[ 17/8 → 9/4 | note:C4 ]",
|
||||
"[ 9/4 → 19/8 | note:G3 ]",
|
||||
"[ 19/8 → 5/2 | note:A3 ]",
|
||||
"[ 5/2 → 21/8 | note:E3 ]",
|
||||
"[ 21/8 → 11/4 | note:F3 ]",
|
||||
"[ 11/4 → 23/8 | note:C3 ]",
|
||||
"[ 23/8 → 3/1 | note:D3 ]",
|
||||
"[ 3/1 → 25/8 | note:Bb3 ]",
|
||||
"[ 25/8 → 13/4 | note:C4 ]",
|
||||
"[ 13/4 → 27/8 | note:G3 ]",
|
||||
"[ 27/8 → 7/2 | note:A3 ]",
|
||||
"[ 7/2 → 29/8 | note:E3 ]",
|
||||
"[ 29/8 → 15/4 | note:F3 ]",
|
||||
"[ 15/4 → 31/8 | note:C3 ]",
|
||||
"[ 31/8 → 4/1 | note:D3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "bite" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 0/1 → 1/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/16 → 1/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/8 → 1/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/8 → 1/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/16 → 1/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/4 → 5/16 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 9/32 → 5/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 5/16 → 11/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 5/16 → 3/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/8 → 7/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/8 → 1/2 | s:cp n:7 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 13/32 → 7/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 7/16 → 1/2 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/2 → 5/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/2 → 5/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 9/16 → 5/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 5/8 → 3/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 5/8 → 3/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 11/16 → 3/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/4 → 25/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/4 → 13/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 13/16 → 7/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 27/32 → 7/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 7/8 → 15/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 7/8 → 1/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 29/32 → 15/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 15/16 → 1/1 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/1 → 9/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 1/1 → 9/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 17/16 → 9/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 9/8 → 5/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 9/8 → 5/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 19/16 → 5/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 5/4 → 21/16 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 41/32 → 21/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 21/16 → 43/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 21/16 → 11/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 11/8 → 23/16 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 11/8 → 3/2 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/2 → 13/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/2 → 13/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 25/16 → 13/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 13/8 → 7/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 13/8 → 7/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 27/16 → 7/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 7/4 → 57/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 7/4 → 29/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 29/16 → 15/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 59/32 → 15/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 15/8 → 31/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 15/8 → 2/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 61/32 → 31/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 31/16 → 2/1 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 2/1 → 17/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 2/1 → 17/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 33/16 → 17/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 17/8 → 9/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 17/8 → 9/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 35/16 → 9/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 9/4 → 37/16 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 73/32 → 37/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 37/16 → 75/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 37/16 → 19/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 19/8 → 39/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 19/8 → 5/2 | s:cp n:7 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 77/32 → 39/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 39/16 → 5/2 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 5/2 → 21/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 5/2 → 21/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 41/16 → 21/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 21/8 → 11/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 21/8 → 11/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 43/16 → 11/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 11/4 → 89/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 11/4 → 45/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 45/16 → 23/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 91/32 → 23/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 23/8 → 47/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 23/8 → 3/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 93/32 → 47/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 47/16 → 3/1 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/1 → 25/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 3/1 → 25/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 49/16 → 25/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 25/8 → 13/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 25/8 → 13/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 51/16 → 13/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 13/4 → 53/16 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 105/32 → 53/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 53/16 → 107/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 53/16 → 27/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 27/8 → 55/16 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 27/8 → 7/2 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 7/2 → 29/8 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 7/2 → 29/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 57/16 → 29/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 29/8 → 15/4 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 29/8 → 15/4 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 59/16 → 15/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 15/4 → 121/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 15/4 → 61/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 61/16 → 31/8 | s:hh bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 123/32 → 31/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 31/8 → 63/16 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 31/8 → 4/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 125/32 → 63/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]",
|
||||
"[ 63/16 → 4/1 | s:bd bank:RolandTR909 speed:1.2 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "bpattack" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]",
|
||||
@@ -1229,6 +1379,16 @@ exports[`runs examples > example "brandBy" example index 0 1`] = `
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "cat" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:e5 ]",
|
||||
"[ 1/1 → 2/1 | note:b4 ]",
|
||||
"[ 2/1 → 5/2 | note:d5 ]",
|
||||
"[ 5/2 → 3/1 | note:c5 ]",
|
||||
"[ 3/1 → 4/1 | note:e5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "cat" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:hh ]",
|
||||
"[ 1/4 → 1/2 | s:hh ]",
|
||||
@@ -1251,16 +1411,6 @@ exports[`runs examples > example "cat" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "cat" example index 0 2`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:e5 ]",
|
||||
"[ 1/1 → 2/1 | note:b4 ]",
|
||||
"[ 2/1 → 5/2 | note:d5 ]",
|
||||
"[ 5/2 → 3/1 | note:c5 ]",
|
||||
"[ 3/1 → 4/1 | note:e5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "ceil" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:42 ]",
|
||||
@@ -2666,6 +2816,43 @@ exports[`runs examples > example "fastGap" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "filter" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh ]",
|
||||
"[ 1/8 → 1/4 | s:hh ]",
|
||||
"[ 1/4 → 3/8 | s:hh ]",
|
||||
"[ 3/8 → 1/2 | s:hh ]",
|
||||
"[ 1/2 → 5/8 | s:hh ]",
|
||||
"[ 5/8 → 3/4 | s:hh ]",
|
||||
"[ 3/4 → 7/8 | s:hh ]",
|
||||
"[ 7/8 → 1/1 | s:oh ]",
|
||||
"[ 1/1 → 9/8 | s:hh ]",
|
||||
"[ 9/8 → 5/4 | s:hh ]",
|
||||
"[ 5/4 → 11/8 | s:hh ]",
|
||||
"[ 11/8 → 3/2 | s:hh ]",
|
||||
"[ 3/2 → 13/8 | s:hh ]",
|
||||
"[ 13/8 → 7/4 | s:hh ]",
|
||||
"[ 7/4 → 15/8 | s:hh ]",
|
||||
"[ 15/8 → 2/1 | s:oh ]",
|
||||
"[ 2/1 → 17/8 | s:hh ]",
|
||||
"[ 17/8 → 9/4 | s:hh ]",
|
||||
"[ 9/4 → 19/8 | s:hh ]",
|
||||
"[ 19/8 → 5/2 | s:hh ]",
|
||||
"[ 5/2 → 21/8 | s:hh ]",
|
||||
"[ 21/8 → 11/4 | s:hh ]",
|
||||
"[ 11/4 → 23/8 | s:hh ]",
|
||||
"[ 23/8 → 3/1 | s:oh ]",
|
||||
"[ 3/1 → 25/8 | s:hh ]",
|
||||
"[ 25/8 → 13/4 | s:hh ]",
|
||||
"[ 13/4 → 27/8 | s:hh ]",
|
||||
"[ 27/8 → 7/2 | s:hh ]",
|
||||
"[ 7/2 → 29/8 | s:hh ]",
|
||||
"[ 29/8 → 15/4 | s:hh ]",
|
||||
"[ 15/4 → 31/8 | s:hh ]",
|
||||
"[ 31/8 → 4/1 | s:oh ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "firstOf" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:g3 ]",
|
||||
@@ -6789,6 +6976,27 @@ exports[`runs examples > example "segment" example index 0 1`] = `
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "seq" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/3 | note:e5 ]",
|
||||
"[ 1/3 → 2/3 | note:b4 ]",
|
||||
"[ 2/3 → 5/6 | note:d5 ]",
|
||||
"[ 5/6 → 1/1 | note:c5 ]",
|
||||
"[ 1/1 → 4/3 | note:e5 ]",
|
||||
"[ 4/3 → 5/3 | note:b4 ]",
|
||||
"[ 5/3 → 11/6 | note:d5 ]",
|
||||
"[ 11/6 → 2/1 | note:c5 ]",
|
||||
"[ 2/1 → 7/3 | note:e5 ]",
|
||||
"[ 7/3 → 8/3 | note:b4 ]",
|
||||
"[ 8/3 → 17/6 | note:d5 ]",
|
||||
"[ 17/6 → 3/1 | note:c5 ]",
|
||||
"[ 3/1 → 10/3 | note:e5 ]",
|
||||
"[ 10/3 → 11/3 | note:b4 ]",
|
||||
"[ 11/3 → 23/6 | note:d5 ]",
|
||||
"[ 23/6 → 4/1 | note:c5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "seq" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh ]",
|
||||
"[ 1/8 → 1/4 | s:hh ]",
|
||||
@@ -6829,24 +7037,23 @@ exports[`runs examples > example "seq" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "seq" example index 0 2`] = `
|
||||
exports[`runs examples > example "seqPLoop" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/3 | note:e5 ]",
|
||||
"[ 1/3 → 2/3 | note:b4 ]",
|
||||
"[ 2/3 → 5/6 | note:d5 ]",
|
||||
"[ 5/6 → 1/1 | note:c5 ]",
|
||||
"[ 1/1 → 4/3 | note:e5 ]",
|
||||
"[ 4/3 → 5/3 | note:b4 ]",
|
||||
"[ 5/3 → 11/6 | note:d5 ]",
|
||||
"[ 11/6 → 2/1 | note:c5 ]",
|
||||
"[ 2/1 → 7/3 | note:e5 ]",
|
||||
"[ 7/3 → 8/3 | note:b4 ]",
|
||||
"[ 8/3 → 17/6 | note:d5 ]",
|
||||
"[ 17/6 → 3/1 | note:c5 ]",
|
||||
"[ 3/1 → 10/3 | note:e5 ]",
|
||||
"[ 10/3 → 11/3 | note:b4 ]",
|
||||
"[ 11/3 → 23/6 | note:d5 ]",
|
||||
"[ 23/6 → 4/1 | note:c5 ]",
|
||||
"[ 0/1 → 1/8 | s:bd ]",
|
||||
"[ 3/8 → 1/2 | s:bd ]",
|
||||
"[ 3/4 → 7/8 | s:bd ]",
|
||||
"[ 1/1 → 9/8 | s:bd ]",
|
||||
"[ 1/1 → 9/8 | s:cp ]",
|
||||
"[ 11/8 → 3/2 | s:bd ]",
|
||||
"[ 11/8 → 3/2 | s:cp ]",
|
||||
"[ 7/4 → 15/8 | s:bd ]",
|
||||
"[ 7/4 → 15/8 | s:cp ]",
|
||||
"[ 2/1 → 17/8 | s:cp ]",
|
||||
"[ 19/8 → 5/2 | s:cp ]",
|
||||
"[ 11/4 → 23/8 | s:cp ]",
|
||||
"[ 3/1 → 25/8 | s:bd ]",
|
||||
"[ 27/8 → 7/2 | s:bd ]",
|
||||
"[ 15/4 → 31/8 | s:bd ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -7523,6 +7730,27 @@ exports[`runs examples > example "squiz" example index 0 1`] = `
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "stack" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | note:e4 ]",
|
||||
"[ 0/1 → 1/1 | note:g3 ]",
|
||||
"[ 0/1 → 1/1 | note:b3 ]",
|
||||
"[ 1/2 → 1/1 | note:d4 ]",
|
||||
"[ 1/1 → 3/2 | note:e4 ]",
|
||||
"[ 1/1 → 2/1 | note:g3 ]",
|
||||
"[ 1/1 → 2/1 | note:b3 ]",
|
||||
"[ 3/2 → 2/1 | note:d4 ]",
|
||||
"[ 2/1 → 5/2 | note:e4 ]",
|
||||
"[ 2/1 → 3/1 | note:g3 ]",
|
||||
"[ 2/1 → 3/1 | note:b3 ]",
|
||||
"[ 5/2 → 3/1 | note:d4 ]",
|
||||
"[ 3/1 → 7/2 | note:e4 ]",
|
||||
"[ 3/1 → 4/1 | note:g3 ]",
|
||||
"[ 3/1 → 4/1 | note:b3 ]",
|
||||
"[ 7/2 → 4/1 | note:d4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "stack" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:c4 ]",
|
||||
"[ 0/1 → 1/4 | s:hh ]",
|
||||
@@ -7563,27 +7791,6 @@ exports[`runs examples > example "stack" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "stack" example index 0 2`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | note:e4 ]",
|
||||
"[ 0/1 → 1/1 | note:g3 ]",
|
||||
"[ 0/1 → 1/1 | note:b3 ]",
|
||||
"[ 1/2 → 1/1 | note:d4 ]",
|
||||
"[ 1/1 → 3/2 | note:e4 ]",
|
||||
"[ 1/1 → 2/1 | note:g3 ]",
|
||||
"[ 1/1 → 2/1 | note:b3 ]",
|
||||
"[ 3/2 → 2/1 | note:d4 ]",
|
||||
"[ 2/1 → 5/2 | note:e4 ]",
|
||||
"[ 2/1 → 3/1 | note:g3 ]",
|
||||
"[ 2/1 → 3/1 | note:b3 ]",
|
||||
"[ 5/2 → 3/1 | note:d4 ]",
|
||||
"[ 3/1 → 7/2 | note:e4 ]",
|
||||
"[ 3/1 → 4/1 | note:g3 ]",
|
||||
"[ 3/1 → 4/1 | note:b3 ]",
|
||||
"[ 7/2 → 4/1 | note:d4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "steps" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:bd ]",
|
||||
@@ -8597,117 +8804,6 @@ exports[`runs examples > example "xfade" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "z1" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:D3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 1/8 → 1/4 | note:E3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 1/4 → 3/8 | note:F#3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 3/8 → 1/2 | note:A3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 1/2 → 5/8 | note:B3 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 5/8 → 3/4 | note:D4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 3/4 → 7/8 | note:E4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 7/8 → 1/1 | note:F#4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 1/1 → 9/8 | note:D3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 9/8 → 5/4 | note:E3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 5/4 → 11/8 | note:F#3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 11/8 → 3/2 | note:A3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 3/2 → 13/8 | note:B3 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 13/8 → 7/4 | note:D4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 7/4 → 15/8 | note:E4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 15/8 → 2/1 | note:F#4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 2/1 → 17/8 | note:D3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 17/8 → 9/4 | note:E3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 9/4 → 19/8 | note:F#3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 19/8 → 5/2 | note:A3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 5/2 → 21/8 | note:B3 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 21/8 → 11/4 | note:D4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 11/4 → 23/8 | note:E4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 23/8 → 3/1 | note:F#4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 3/1 → 25/8 | note:D3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 25/8 → 13/4 | note:E3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 13/4 → 27/8 | note:F#3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 27/8 → 7/2 | note:A3 s:supersaw z1:0.01 release:0.5 ]",
|
||||
"[ 7/2 → 29/8 | note:B3 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 29/8 → 15/4 | note:D4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 15/4 → 31/8 | note:E4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
"[ 31/8 → 4/1 | note:F#4 s:supersaw z1:0.75 release:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "z2" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:D3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 1/8 → 1/4 | note:E3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 1/4 → 3/8 | note:F#3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 3/8 → 1/2 | note:A3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 1/2 → 5/8 | note:B3 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 5/8 → 3/4 | note:D4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 3/4 → 7/8 | note:E4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 7/8 → 1/1 | note:F#4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 1/1 → 9/8 | note:D3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 9/8 → 5/4 | note:E3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 5/4 → 11/8 | note:F#3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 11/8 → 3/2 | note:A3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 3/2 → 13/8 | note:B3 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 13/8 → 7/4 | note:D4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 7/4 → 15/8 | note:E4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 15/8 → 2/1 | note:F#4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 2/1 → 17/8 | note:D3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 17/8 → 9/4 | note:E3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 9/4 → 19/8 | note:F#3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 19/8 → 5/2 | note:A3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 5/2 → 21/8 | note:B3 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 21/8 → 11/4 | note:D4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 11/4 → 23/8 | note:E4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 23/8 → 3/1 | note:F#4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 3/1 → 25/8 | note:D3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 25/8 → 13/4 | note:E3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 13/4 → 27/8 | note:F#3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 27/8 → 7/2 | note:A3 s:supersaw z2:2 release:0.5 ]",
|
||||
"[ 7/2 → 29/8 | note:B3 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 29/8 → 15/4 | note:D4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 15/4 → 31/8 | note:E4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
"[ 31/8 → 4/1 | note:F#4 s:supersaw z2:0.7 release:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "z3" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:D3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 1/8 → 1/4 | note:E3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 1/4 → 3/8 | note:F#3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 3/8 → 1/2 | note:A3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 1/2 → 5/8 | note:B3 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 5/8 → 3/4 | note:D4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 3/4 → 7/8 | note:E4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 7/8 → 1/1 | note:F#4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 1/1 → 9/8 | note:D3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 9/8 → 5/4 | note:E3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 5/4 → 11/8 | note:F#3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 11/8 → 3/2 | note:A3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 3/2 → 13/8 | note:B3 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 13/8 → 7/4 | note:D4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 7/4 → 15/8 | note:E4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 15/8 → 2/1 | note:F#4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 2/1 → 17/8 | note:D3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 17/8 → 9/4 | note:E3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 9/4 → 19/8 | note:F#3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 19/8 → 5/2 | note:A3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 5/2 → 21/8 | note:B3 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 21/8 → 11/4 | note:D4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 11/4 → 23/8 | note:E4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 23/8 → 3/1 | note:F#4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 3/1 → 25/8 | note:D3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 25/8 → 13/4 | note:E3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 13/4 → 27/8 | note:F#3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 27/8 → 7/2 | note:A3 s:supersaw z3:0.1 release:0.5 ]",
|
||||
"[ 7/2 → 29/8 | note:B3 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 29/8 → 15/4 | note:D4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 15/4 → 31/8 | note:E4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
"[ 31/8 → 4/1 | note:F#4 s:supersaw z3:0.75 release:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "zoom" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/6 | s:hh ]",
|
||||
|
||||
@@ -3,11 +3,6 @@ import { configDefaults, defineConfig } from 'vitest/config';
|
||||
/// <reference types="vitest" />
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// ...
|
||||
deps: {
|
||||
registerNodeLoader: true,
|
||||
},
|
||||
threads: false,
|
||||
reporters: 'verbose',
|
||||
isolate: false,
|
||||
silent: true,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"devToolbar": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
Vendored
-290
@@ -1,290 +0,0 @@
|
||||
declare module 'astro:content' {
|
||||
interface Render {
|
||||
'.mdx': Promise<{
|
||||
Content: import('astro').MarkdownInstance<{}>['Content'];
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'astro:content' {
|
||||
interface Render {
|
||||
'.md': Promise<{
|
||||
Content: import('astro').MarkdownInstance<{}>['Content'];
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'astro:content' {
|
||||
export { z } from 'astro/zod';
|
||||
|
||||
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||
|
||||
export type CollectionKey = keyof AnyEntryMap;
|
||||
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
||||
|
||||
export type ContentCollectionKey = keyof ContentEntryMap;
|
||||
export type DataCollectionKey = keyof DataEntryMap;
|
||||
|
||||
// This needs to be in sync with ImageMetadata
|
||||
export type ImageFunction = () => import('astro/zod').ZodObject<{
|
||||
src: import('astro/zod').ZodString;
|
||||
width: import('astro/zod').ZodNumber;
|
||||
height: import('astro/zod').ZodNumber;
|
||||
format: import('astro/zod').ZodUnion<
|
||||
[
|
||||
import('astro/zod').ZodLiteral<'png'>,
|
||||
import('astro/zod').ZodLiteral<'jpg'>,
|
||||
import('astro/zod').ZodLiteral<'jpeg'>,
|
||||
import('astro/zod').ZodLiteral<'tiff'>,
|
||||
import('astro/zod').ZodLiteral<'webp'>,
|
||||
import('astro/zod').ZodLiteral<'gif'>,
|
||||
import('astro/zod').ZodLiteral<'svg'>,
|
||||
import('astro/zod').ZodLiteral<'avif'>,
|
||||
]
|
||||
>;
|
||||
}>;
|
||||
|
||||
type BaseSchemaWithoutEffects =
|
||||
| import('astro/zod').AnyZodObject
|
||||
| import('astro/zod').ZodUnion<[BaseSchemaWithoutEffects, ...BaseSchemaWithoutEffects[]]>
|
||||
| import('astro/zod').ZodDiscriminatedUnion<string, import('astro/zod').AnyZodObject[]>
|
||||
| import('astro/zod').ZodIntersection<BaseSchemaWithoutEffects, BaseSchemaWithoutEffects>;
|
||||
|
||||
type BaseSchema =
|
||||
| BaseSchemaWithoutEffects
|
||||
| import('astro/zod').ZodEffects<BaseSchemaWithoutEffects>;
|
||||
|
||||
export type SchemaContext = { image: ImageFunction };
|
||||
|
||||
type DataCollectionConfig<S extends BaseSchema> = {
|
||||
type: 'data';
|
||||
schema?: S | ((context: SchemaContext) => S);
|
||||
};
|
||||
|
||||
type ContentCollectionConfig<S extends BaseSchema> = {
|
||||
type?: 'content';
|
||||
schema?: S | ((context: SchemaContext) => S);
|
||||
};
|
||||
|
||||
type CollectionConfig<S> = ContentCollectionConfig<S> | DataCollectionConfig<S>;
|
||||
|
||||
export function defineCollection<S extends BaseSchema>(
|
||||
input: CollectionConfig<S>
|
||||
): CollectionConfig<S>;
|
||||
|
||||
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
||||
ContentEntryMap[C]
|
||||
>['slug'];
|
||||
|
||||
export function getEntryBySlug<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
// Note that this has to accept a regular string too, for SSR
|
||||
entrySlug: E
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
|
||||
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
||||
collection: C,
|
||||
entryId: E
|
||||
): Promise<CollectionEntry<C>>;
|
||||
|
||||
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => entry is E
|
||||
): Promise<E[]>;
|
||||
export function getCollection<C extends keyof AnyEntryMap>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => unknown
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(entry: {
|
||||
collection: C;
|
||||
slug: E;
|
||||
}): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(entry: {
|
||||
collection: C;
|
||||
id: E;
|
||||
}): E extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
slug: E
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
id: E
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
|
||||
/** Resolve an array of entry references from the same collection */
|
||||
export function getEntries<C extends keyof ContentEntryMap>(
|
||||
entries: {
|
||||
collection: C;
|
||||
slug: ValidContentEntrySlug<C>;
|
||||
}[]
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
export function getEntries<C extends keyof DataEntryMap>(
|
||||
entries: {
|
||||
collection: C;
|
||||
id: keyof DataEntryMap[C];
|
||||
}[]
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function reference<C extends keyof AnyEntryMap>(
|
||||
collection: C
|
||||
): import('astro/zod').ZodEffects<
|
||||
import('astro/zod').ZodString,
|
||||
C extends keyof ContentEntryMap
|
||||
? {
|
||||
collection: C;
|
||||
slug: ValidContentEntrySlug<C>;
|
||||
}
|
||||
: {
|
||||
collection: C;
|
||||
id: keyof DataEntryMap[C];
|
||||
}
|
||||
>;
|
||||
// Allow generic `string` to avoid excessive type errors in the config
|
||||
// if `dev` is not running to update as you edit.
|
||||
// Invalid collection names will be caught at build time.
|
||||
export function reference<C extends string>(
|
||||
collection: C
|
||||
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
||||
|
||||
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
||||
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||
>;
|
||||
|
||||
type ContentEntryMap = {
|
||||
"blog": {
|
||||
"release-0.0.2-schwindlig.mdx": {
|
||||
id: "release-0.0.2-schwindlig.mdx";
|
||||
slug: "release-002-schwindlig";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.0.2.1-stuermisch.mdx": {
|
||||
id: "release-0.0.2.1-stuermisch.mdx";
|
||||
slug: "release-0021-stuermisch";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.0.3-maelstrom.mdx": {
|
||||
id: "release-0.0.3-maelstrom.mdx";
|
||||
slug: "release-003-maelstrom";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.0.4-gischt.mdx": {
|
||||
id: "release-0.0.4-gischt.mdx";
|
||||
slug: "release-004-gischt";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.3.0-donauwelle.mdx": {
|
||||
id: "release-0.3.0-donauwelle.mdx";
|
||||
slug: "release-030-donauwelle";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.4.0-brandung.mdx": {
|
||||
id: "release-0.4.0-brandung.mdx";
|
||||
slug: "release-040-brandung";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.5.0-wirbel.mdx": {
|
||||
id: "release-0.5.0-wirbel.mdx";
|
||||
slug: "release-050-wirbel";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.6.0-zimtschnecke.mdx": {
|
||||
id: "release-0.6.0-zimtschnecke.mdx";
|
||||
slug: "release-060-zimtschnecke";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.7.0-zuckerguss.mdx": {
|
||||
id: "release-0.7.0-zuckerguss.mdx";
|
||||
slug: "release-070-zuckerguss";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.8.0-himbeermuffin.mdx": {
|
||||
id: "release-0.8.0-himbeermuffin.mdx";
|
||||
slug: "release-080-himbeermuffin";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-0.9.0-bananenbrot.mdx": {
|
||||
id: "release-0.9.0-bananenbrot.mdx";
|
||||
slug: "release-090-bananenbrot";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"release-1.0.0-geburtstagskuchen.mdx": {
|
||||
id: "release-1.0.0-geburtstagskuchen.mdx";
|
||||
slug: "release-100-geburtstagskuchen";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
"year-2.mdx": {
|
||||
id: "year-2.mdx";
|
||||
slug: "year-2";
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">
|
||||
} & { render(): Render[".mdx"] };
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
type DataEntryMap = {
|
||||
|
||||
};
|
||||
|
||||
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
||||
|
||||
type ContentConfig = typeof import("../src/content/config");
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
.astro
|
||||
# build output
|
||||
dist/
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@strudel/mini": "workspace:*",
|
||||
"@strudel/osc": "workspace:*",
|
||||
"@strudel/serial": "workspace:*",
|
||||
"@strudel/shader": "workspace:*",
|
||||
"@strudel/soundfonts": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Loader from '@src/repl/components/Loader';
|
||||
import { Panel } from '@src/repl/components/panel/Panel';
|
||||
import { HorizontalPanel } from '@src/repl/components/panel/Panel';
|
||||
import { Code } from '@src/repl/components/Code';
|
||||
import BigPlayButton from '@src/repl/components/BigPlayButton';
|
||||
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
|
||||
@@ -20,7 +20,7 @@ export default function UdelsEditor(Props) {
|
||||
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
||||
</div>
|
||||
<UserFacingErrorMessage error={error} />
|
||||
<Panel context={context} />
|
||||
<HorizontalPanel context={context} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ author: froos
|
||||
- fix: 💄 Enhance visualisation of the Tutorial on mobile by @puria in https://github.com/tidalcycles/strudel/pull/19
|
||||
- Stateful queries and events (WIP) by @yaxu in https://github.com/tidalcycles/strudel/pull/14
|
||||
- Fix resolveState by @yaxu in https://github.com/tidalcycles/strudel/pull/22
|
||||
- added \_asNumber + interprete numbers as midi by @felixroos in https://github.com/tidalcycles/strudel/pull/21
|
||||
- added \_asNumber + interpret numbers as midi by @felixroos in https://github.com/tidalcycles/strudel/pull/21
|
||||
- Update package.json by @ChiakiUehira in https://github.com/tidalcycles/strudel/pull/23
|
||||
- packaging by @felixroos in https://github.com/tidalcycles/strudel/pull/24
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ author: froos
|
||||
|
||||
- support freq in pianoroll by @felixroos in https://github.com/tidalcycles/strudel/pull/308
|
||||
- ICLC2023 paper WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/306
|
||||
- fix: copy share link to clipboard was broken for some browers by @felixroos in https://github.com/tidalcycles/strudel/pull/311
|
||||
- fix: copy share link to clipboard was broken for some browsers by @felixroos in https://github.com/tidalcycles/strudel/pull/311
|
||||
- Jsdoc component by @felixroos in https://github.com/tidalcycles/strudel/pull/312
|
||||
- object support for .scale by @felixroos in https://github.com/tidalcycles/strudel/pull/307
|
||||
- Astro build by @felixroos in https://github.com/tidalcycles/strudel/pull/315
|
||||
|
||||
@@ -138,7 +138,7 @@ Plenty of things have been added to the docs, including a [showcase of what peop
|
||||
- Export patterns + ui tweaks by @felixroos in https://github.com/tidalcycles/strudel/pull/855
|
||||
- Pattern organization by @felixroos in https://github.com/tidalcycles/strudel/pull/858
|
||||
- Sound Import from local file system by @daslyfe in https://github.com/tidalcycles/strudel/pull/839
|
||||
- bugfix: suspend and close exisiting audio context when changing interface by @daslyfe in https://github.com/tidalcycles/strudel/pull/882
|
||||
- bugfix: suspend and close existing audio context when changing interface by @daslyfe in https://github.com/tidalcycles/strudel/pull/882
|
||||
- add root mode for voicings by @felixroos in https://github.com/tidalcycles/strudel/pull/887
|
||||
- scales can now be anchored by @felixroos in https://github.com/tidalcycles/strudel/pull/888
|
||||
- add dough function for raw dsp by @felixroos in https://github.com/tidalcycles/strudel/pull/707 (experimental)
|
||||
@@ -195,7 +195,7 @@ Plenty of things have been added to the docs, including a [showcase of what peop
|
||||
- Update tauri.yml workflow file by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/705
|
||||
- vite-vanilla-repl readme fix by @felixroos in https://github.com/tidalcycles/strudel/pull/737
|
||||
- completely revert config mess by @felixroos in https://github.com/tidalcycles/strudel/pull/745
|
||||
- hopefully fix trainling slashes bug by @felixroos in https://github.com/tidalcycles/strudel/pull/753
|
||||
- hopefully fix trailing slashes bug by @felixroos in https://github.com/tidalcycles/strudel/pull/753
|
||||
- Update vite pwa by @felixroos in https://github.com/tidalcycles/strudel/pull/772
|
||||
- Update to Astro 3 by @felixroos in https://github.com/tidalcycles/strudel/pull/775
|
||||
- support multiple named serial connections, change default baudrate by @yaxu in https://github.com/tidalcycles/strudel/pull/551
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||
import { Icon } from './Icon';
|
||||
import { silence, noteToMidi, _mod } from '@strudel/core';
|
||||
import { clearHydra } from '@strudel/hydra';
|
||||
import { getDrawContext, getPunchcardPainter } from '@strudel/draw';
|
||||
import { transpiler } from '@strudel/transpiler';
|
||||
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
|
||||
@@ -37,7 +36,7 @@ export function MiniRepl({
|
||||
const shouldShowCanvas = !!punchcard;
|
||||
const canvasId = shouldShowCanvas ? useMemo(() => `canvas-${id}`, [id]) : null;
|
||||
autodraw = !!punchcard || !!claviature || !!autodraw;
|
||||
drawTime = drawTime ?? punchcard ? [0, 4] : [-2, 2];
|
||||
drawTime = (drawTime ?? punchcard) ? [0, 4] : [-2, 2];
|
||||
if (claviature) {
|
||||
drawTime = [0, 0];
|
||||
}
|
||||
@@ -120,6 +119,7 @@ export function MiniRepl({
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
|
||||
started ? 'animate-pulse' : '',
|
||||
)}
|
||||
aria-label={started ? 'stop' : 'play'}
|
||||
onClick={() => editorRef.current?.toggle()}
|
||||
>
|
||||
<Icon type={started ? 'stop' : 'play'} />
|
||||
@@ -129,6 +129,7 @@ export function MiniRepl({
|
||||
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
|
||||
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
aria-label="update"
|
||||
onClick={() => editorRef.current?.evaluate()}
|
||||
>
|
||||
<Icon type="refresh" />
|
||||
@@ -140,6 +141,7 @@ export function MiniRepl({
|
||||
className={
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
|
||||
}
|
||||
aria-label="previous example"
|
||||
onClick={() => changeTune(tuneIndex - 1)}
|
||||
>
|
||||
<div className="rotate-180">
|
||||
@@ -150,6 +152,7 @@ export function MiniRepl({
|
||||
className={
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background'
|
||||
}
|
||||
aria-label="next example"
|
||||
onClick={() => changeTune(tuneIndex + 1)}
|
||||
>
|
||||
<Icon type="skip" />
|
||||
|
||||
@@ -25,26 +25,14 @@ These are the equivalents used by the Mini Notation:
|
||||
|
||||
<JsDoc client:idle name="cat" h={0} />
|
||||
|
||||
You can also use cat as a chained function like this:
|
||||
|
||||
<JsDoc client:idle name="Pattern.cat" h={0} hideDescription />
|
||||
|
||||
## seq
|
||||
|
||||
<JsDoc client:idle name="seq" h={0} />
|
||||
|
||||
Or as a chained function:
|
||||
|
||||
<JsDoc client:idle name="Pattern.seq" h={0} hideDescription />
|
||||
|
||||
## stack
|
||||
|
||||
<JsDoc client:idle name="stack" h={0} />
|
||||
|
||||
As a chained function:
|
||||
|
||||
<JsDoc client:idle name="Pattern.stack" h={0} hideDescription />
|
||||
|
||||
## s_cat
|
||||
|
||||
<JsDoc client:idle name="s_cat" h={0} />
|
||||
|
||||
@@ -43,7 +43,7 @@ export function Header({ context, embedded = false }) {
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx('mt-[1px]', started && 'animate-spin', 'cursor-pointer')}
|
||||
className={cx('mt-[1px]', started && 'animate-spin', 'cursor-pointer', isZen && 'fixed top-2 right-4')}
|
||||
onClick={() => {
|
||||
if (!isEmbedded) {
|
||||
setIsZen(!isZen);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Loader from '@src/repl/components/Loader';
|
||||
import { Panel } from '@src/repl/components/panel/Panel';
|
||||
import { HorizontalPanel, VerticalPanel } from '@src/repl/components/panel/Panel';
|
||||
import { Code } from '@src/repl/components/Code';
|
||||
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
|
||||
import { Header } from './Header';
|
||||
@@ -21,10 +21,10 @@ export default function ReplEditor(Props) {
|
||||
<Header context={context} />
|
||||
<div className="grow flex relative overflow-hidden">
|
||||
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
||||
{panelPosition === 'right' && <Panel context={context} />}
|
||||
{panelPosition === 'right' && <VerticalPanel context={context} />}
|
||||
</div>
|
||||
<UserFacingErrorMessage error={error} />
|
||||
{panelPosition === 'bottom' && <Panel context={context} />}
|
||||
{panelPosition === 'bottom' && <HorizontalPanel context={context} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import cx from '@src/cx.mjs';
|
||||
|
||||
export function ConsoleTab({ log }) {
|
||||
return (
|
||||
<div id="console-tab" className="break-all px-4 dark:text-white text-stone-900 text-sm">
|
||||
<pre>{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
|
||||
<div id="console-tab" className="break-all px-4 dark:text-white text-stone-900 text-sm py-2">
|
||||
<pre aria-hidden="true">{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
|
||||
██╔════╝╚══██╔══╝██╔══██╗██║ ██║██╔══██╗██╔════╝██║
|
||||
███████╗ ██║ ██████╔╝██║ ██║██║ ██║█████╗ ██║
|
||||
╚════██║ ██║ ██╔══██╗██║ ██║██║ ██║██╔══╝ ██║
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
|
||||
import { logger } from '@strudel/core';
|
||||
import useEvent from '@src/useEvent.mjs';
|
||||
import cx from '@src/cx.mjs';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useLayoutEffect, useEffect, useRef, useState } from 'react';
|
||||
import { setActiveFooter, useSettings } from '../../../settings.mjs';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { setPanelPinned, setActiveFooter as setTab, useSettings } from '../../../settings.mjs';
|
||||
import { ConsoleTab } from './ConsoleTab';
|
||||
import { FilesTab } from './FilesTab';
|
||||
import { Reference } from './Reference';
|
||||
@@ -12,33 +11,87 @@ import { SettingsTab } from './SettingsTab';
|
||||
import { SoundsTab } from './SoundsTab';
|
||||
import { WelcomeTab } from './WelcomeTab';
|
||||
import { PatternsTab } from './PatternsTab';
|
||||
import useClient from '@src/useClient.mjs';
|
||||
|
||||
// https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
|
||||
export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
import { ChevronLeftIcon } from '@heroicons/react/16/solid';
|
||||
|
||||
const TAURI = typeof window !== 'undefined' && window.__TAURI__;
|
||||
|
||||
export function Panel({ context }) {
|
||||
const footerContent = useRef();
|
||||
export function HorizontalPanel({ context }) {
|
||||
const settings = useSettings();
|
||||
const { isPanelPinned: pinned, activeFooter: tab } = settings;
|
||||
|
||||
return (
|
||||
<PanelNav
|
||||
className={cx(
|
||||
'hover:max-h-[360px] hover:min-h-[360px] justify-between flex flex-col',
|
||||
pinned ? `min-h-[360px] max-h-[360px]` : 'min-h-10 max-h-10',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-full overflow-auto ">
|
||||
<PanelContent context={context} tab={tab} />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between min-h-10 max-h-10 pr-2 items-center">
|
||||
<Tabs setTab={setTab} tab={tab} pinned={pinned} />
|
||||
<PinButton pinned={pinned} setPinned={setPanelPinned} />
|
||||
</div>
|
||||
</PanelNav>
|
||||
);
|
||||
}
|
||||
|
||||
export function VerticalPanel({ context }) {
|
||||
const settings = useSettings();
|
||||
const { isPanelPinned: pinned, activeFooter: tab } = settings;
|
||||
|
||||
return (
|
||||
<PanelNav
|
||||
className={cx(
|
||||
'hover:min-w-[min(600px,80vw)] hover:max-w-[min(600px,80vw)]',
|
||||
pinned ? `min-w-[min(600px,80vw)] max-w-[min(600px,80vw)]` : 'min-w-8',
|
||||
)}
|
||||
>
|
||||
<div className={cx('group-hover:flex flex-col h-full', pinned ? 'flex' : 'hidden')}>
|
||||
<div className="flex justify-between w-full ">
|
||||
<Tabs setTab={setTab} tab={tab} pinned={pinned} />
|
||||
<PinButton pinned={pinned} setPinned={setPanelPinned} />
|
||||
</div>
|
||||
|
||||
<div className="overflow-auto h-full">
|
||||
<PanelContent context={context} tab={tab} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx(pinned ? 'hidden' : 'flex flex-col items-center justify-center h-full group-hover:hidden ')}>
|
||||
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
|
||||
</div>
|
||||
</PanelNav>
|
||||
);
|
||||
}
|
||||
|
||||
const tabNames = {
|
||||
welcome: 'intro',
|
||||
patterns: 'patterns',
|
||||
sounds: 'sounds',
|
||||
reference: 'reference',
|
||||
console: 'console',
|
||||
settings: 'settings',
|
||||
};
|
||||
if (TAURI) {
|
||||
tabNames.files = 'files';
|
||||
}
|
||||
|
||||
function PanelNav({ children, className, ...props }) {
|
||||
return (
|
||||
<nav
|
||||
aria-label="Settings Menu"
|
||||
className={cx('bg-lineHighlight group transition-all overflow-x-auto', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function PanelContent({ context, tab }) {
|
||||
const [log, setLog] = useState([]);
|
||||
const { activeFooter, isZen, panelPosition } = useSettings();
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (footerContent.current && activeFooter === 'console') {
|
||||
// scroll log box to bottom when log changes
|
||||
footerContent.current.scrollTop = footerContent.current?.scrollHeight;
|
||||
}
|
||||
}, [log, activeFooter]);
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (!footerContent.current) {
|
||||
} else if (activeFooter === 'console') {
|
||||
footerContent.current.scrollTop = footerContent.current?.scrollHeight;
|
||||
} else {
|
||||
footerContent.current.scrollTop = 0;
|
||||
}
|
||||
}, [activeFooter]);
|
||||
|
||||
useLogger(
|
||||
useCallback((e) => {
|
||||
const { message, type, data } = e.detail;
|
||||
@@ -60,66 +113,72 @@ export function Panel({ context }) {
|
||||
}, []),
|
||||
);
|
||||
|
||||
const PanelTab = ({ children, name, label }) => (
|
||||
switch (tab) {
|
||||
case tabNames.patterns:
|
||||
return <PatternsTab context={context} />;
|
||||
case tabNames.console:
|
||||
return <ConsoleTab log={log} />;
|
||||
case tabNames.sounds:
|
||||
return <SoundsTab />;
|
||||
case tabNames.reference:
|
||||
return <Reference />;
|
||||
case tabNames.settings:
|
||||
return <SettingsTab started={context.started} />;
|
||||
case tabNames.files:
|
||||
return <FilesTab />;
|
||||
default:
|
||||
return <WelcomeTab context={context} />;
|
||||
}
|
||||
}
|
||||
|
||||
function PanelTab({ label, isSelected, onClick }) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={() => setActiveFooter(name)}
|
||||
onClick={onClick}
|
||||
className={cx(
|
||||
'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b',
|
||||
activeFooter === name ? 'border-foreground' : 'border-transparent',
|
||||
isSelected ? 'border-foreground' : 'border-transparent',
|
||||
)}
|
||||
>
|
||||
{label || name}
|
||||
{label}
|
||||
</div>
|
||||
{activeFooter === name && <>{children}</>}
|
||||
</>
|
||||
);
|
||||
const client = useClient();
|
||||
if (isZen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isActive = activeFooter !== '';
|
||||
|
||||
let positions = {
|
||||
right: cx('max-w-full flex-grow-0 flex-none overflow-hidden', isActive ? 'w-[600px] h-full' : 'absolute right-0'),
|
||||
bottom: cx('relative', isActive ? 'h-[360px] min-h-[360px]' : ''),
|
||||
};
|
||||
if (!client) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function Tabs({ setTab, tab }) {
|
||||
return (
|
||||
<nav className={cx('bg-lineHighlight z-[10] flex flex-col', positions[panelPosition])}>
|
||||
<div className="flex justify-between px-2">
|
||||
<div className={cx('flex select-none max-w-full overflow-auto', activeFooter && 'pb-2')}>
|
||||
<PanelTab name="intro" label="welcome" />
|
||||
<PanelTab name="patterns" />
|
||||
<PanelTab name="sounds" />
|
||||
<PanelTab name="console" />
|
||||
<PanelTab name="reference" />
|
||||
<PanelTab name="settings" />
|
||||
{TAURI && <PanelTab name="files" />}
|
||||
</div>
|
||||
{activeFooter !== '' && (
|
||||
<button onClick={() => setActiveFooter('')} className="text-foreground px-2" aria-label="Close Panel">
|
||||
<XMarkIcon className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{activeFooter !== '' && (
|
||||
<div className="relative overflow-hidden">
|
||||
<div className="text-white overflow-auto h-full max-w-full" ref={footerContent}>
|
||||
{activeFooter === 'intro' && <WelcomeTab context={context} />}
|
||||
{activeFooter === 'patterns' && <PatternsTab context={context} />}
|
||||
{activeFooter === 'console' && <ConsoleTab log={log} />}
|
||||
{activeFooter === 'sounds' && <SoundsTab />}
|
||||
{activeFooter === 'reference' && <Reference />}
|
||||
{activeFooter === 'settings' && <SettingsTab started={context.started} />}
|
||||
{activeFooter === 'files' && <FilesTab />}
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx('flex select-none max-w-full overflow-auto pb-2')}>
|
||||
{Object.keys(tabNames).map((key) => {
|
||||
const val = tabNames[key];
|
||||
return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PinButton({ pinned, setPinned }) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setPinned(!pinned)}
|
||||
className={cx(
|
||||
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
|
||||
pinned ? 'flex' : 'hidden',
|
||||
)}
|
||||
</nav>
|
||||
aria-label="Pin Settings Menu"
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill={'currentColor'}
|
||||
strokeWidth="0"
|
||||
className="w-full h-full"
|
||||
opacity={pinned ? 1 : '.3'}
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M9.828.722a.5.5 0 0 1 .354.146l4.95 4.95a.5.5 0 0 1 0 .707c-.48.48-1.072.588-1.503.588-.177 0-.335-.018-.46-.039l-3.134 3.134a6 6 0 0 1 .16 1.013c.046.702-.032 1.687-.72 2.375a.5.5 0 0 1-.707 0l-2.829-2.828-3.182 3.182c-.195.195-1.219.902-1.414.707s.512-1.22.707-1.414l3.182-3.182-2.828-2.829a.5.5 0 0 1 0-.707c.688-.688 1.673-.767 2.375-.72a6 6 0 0 1 1.013.16l3.134-3.133a3 3 0 0 1-.04-.461c0-.43.108-1.022.589-1.503a.5.5 0 0 1 .353-.146"></path>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
|
||||
|
||||
function ActionButton({ children, onClick, label, labelIsHidden }) {
|
||||
return (
|
||||
<button className="hover:opacity-50" onClick={onClick} title={label}>
|
||||
<button className="hover:opacity-50 text-nowrap" onClick={onClick} title={label}>
|
||||
{labelIsHidden !== true && label}
|
||||
{children}
|
||||
</button>
|
||||
@@ -102,7 +102,7 @@ export function PatternsTab({ context }) {
|
||||
const autoResetPatternOnChange = !isUdels();
|
||||
|
||||
return (
|
||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-2 pb-4 flex flex-col overflow-hidden max-h-full">
|
||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-2 flex flex-col overflow-hidden max-h-full h-full">
|
||||
<ButtonGroup
|
||||
value={patternFilter}
|
||||
onChange={(value) => settingsMap.setKey('patternFilter', value)}
|
||||
@@ -155,7 +155,7 @@ export function PatternsTab({ context }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section className="flex overflow-y-scroll max-h-full flex-col">
|
||||
<section className="flex overflow-y-auto max-h-full flex-grow flex-col">
|
||||
{patternFilter === patternFilterName.user && (
|
||||
<PatternButtons
|
||||
onClick={(id) =>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import jsdocJson from '../../../../../doc.json';
|
||||
const visibleFunctions = jsdocJson.docs
|
||||
const availableFunctions = jsdocJson.docs
|
||||
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
|
||||
.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
|
||||
|
||||
@@ -10,25 +12,50 @@ const getInnerText = (html) => {
|
||||
};
|
||||
|
||||
export function Reference() {
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const visibleFunctions = useMemo(() => {
|
||||
return availableFunctions.filter((entry) => {
|
||||
if (!search) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return entry.name.includes(search) || (entry.synonyms?.some((s) => s.includes(search)) ?? false);
|
||||
});
|
||||
}, [search]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full pt-2 text-foreground overflow-hidden">
|
||||
<div className="w-42 flex-none h-full overflow-y-auto overflow-x-hidden pr-4">
|
||||
{visibleFunctions.map((entry, i) => (
|
||||
<a
|
||||
key={i}
|
||||
className="cursor-pointer block hover:bg-lineHighlight py-1 px-4"
|
||||
onClick={() => {
|
||||
const el = document.getElementById(`doc-${i}`);
|
||||
const container = document.getElementById('reference-container');
|
||||
container.scrollTo(0, el.offsetTop);
|
||||
}}
|
||||
>
|
||||
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
|
||||
</a>
|
||||
))}
|
||||
<div className="flex h-full w-full p-2 text-foreground overflow-hidden">
|
||||
<div className="h-full flex flex-col gap-2 w-1/3 max-w-72 ">
|
||||
<div class="w-full flex">
|
||||
<input
|
||||
className="w-full p-1 bg-background rounded-md border-none"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onInput={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
|
||||
{visibleFunctions.map((entry, i) => (
|
||||
<a
|
||||
key={i}
|
||||
className="cursor-pointer flex-none hover:bg-lineHighlight overflow-x-hidden px-1 text-ellipsis"
|
||||
onClick={() => {
|
||||
const el = document.getElementById(`doc-${i}`);
|
||||
const container = document.getElementById('reference-container');
|
||||
container.scrollTo(0, el.offsetTop);
|
||||
}}
|
||||
>
|
||||
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative" id="reference-container">
|
||||
<div className="prose dark:prose-invert max-w-full pr-4">
|
||||
<div
|
||||
className="break-normal flex-grow flex-col overflow-y-auto overflow-x-hidden px-2 flex relative"
|
||||
id="reference-container"
|
||||
>
|
||||
<div className="prose dark:prose-invert min-w-full px-1 ">
|
||||
<h2>API Reference</h2>
|
||||
<p>
|
||||
This is the long list functions you can use! Remember that you don't need to remember all of those and that
|
||||
|
||||
@@ -105,7 +105,7 @@ export function SettingsTab({ started }) {
|
||||
const shouldAlwaysSync = isUdels();
|
||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||
return (
|
||||
<div className="text-foreground p-4 space-y-4">
|
||||
<div className="text-foreground p-4 space-y-4 w-full">
|
||||
{canChangeAudioDevice && (
|
||||
<FormItem label="Audio Output Device">
|
||||
<AudioDeviceSelector
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import useEvent from '@src/useEvent.mjs';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { getAudioContext, soundMap, connectToDestination } from '@strudel/webaudio';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { settingsMap, useSettings } from '../../../settings.mjs';
|
||||
import { ButtonGroup } from './Forms.jsx';
|
||||
import ImportSoundsButton from './ImportSoundsButton.jsx';
|
||||
@@ -12,15 +12,20 @@ const getSamples = (samples) =>
|
||||
export function SoundsTab() {
|
||||
const sounds = useStore(soundMap);
|
||||
const { soundsFilter } = useSettings();
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const soundEntries = useMemo(() => {
|
||||
let filtered = Object.entries(sounds)
|
||||
.filter(([key]) => !key.startsWith('_'))
|
||||
.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
if (!sounds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let filtered = Object.entries(sounds)
|
||||
.filter(([key]) => !key.startsWith('_'))
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.filter(([name]) => name.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
if (soundsFilter === 'user') {
|
||||
return filtered.filter(([key, { data }]) => !data.prebake);
|
||||
return filtered.filter(([_, { data }]) => !data.prebake);
|
||||
}
|
||||
if (soundsFilter === 'drums') {
|
||||
return filtered.filter(([_, { data }]) => data.type === 'sample' && data.tag === 'drum-machines');
|
||||
@@ -32,9 +37,11 @@ export function SoundsTab() {
|
||||
return filtered.filter(([_, { data }]) => ['synth', 'soundfont'].includes(data.type));
|
||||
}
|
||||
return filtered;
|
||||
}, [sounds, soundsFilter]);
|
||||
}, [sounds, soundsFilter, search]);
|
||||
|
||||
// holds mutable ref to current triggered sound
|
||||
const trigRef = useRef();
|
||||
|
||||
// stop current sound on mouseup
|
||||
useEvent('mouseup', () => {
|
||||
const t = trigRef.current;
|
||||
@@ -43,8 +50,16 @@ export function SoundsTab() {
|
||||
ref?.stop(getAudioContext().currentTime + 0.01);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900">
|
||||
<input
|
||||
className="w-full p-1 bg-background rounded-md pb-2"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="pb-2 flex shrink-0 flex-wrap">
|
||||
<ButtonGroup
|
||||
value={soundsFilter}
|
||||
@@ -58,7 +73,8 @@ export function SoundsTab() {
|
||||
></ButtonGroup>
|
||||
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
|
||||
</div>
|
||||
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal">
|
||||
|
||||
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal pb-2">
|
||||
{soundEntries.map(([name, { data, onTrigger }]) => {
|
||||
return (
|
||||
<span
|
||||
|
||||
@@ -5,7 +5,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
|
||||
|
||||
export function WelcomeTab({ context }) {
|
||||
return (
|
||||
<div className="prose dark:prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
|
||||
<div className="prose dark:prose-invert min-w-full pt-2 font-sans pb-8 px-4 ">
|
||||
<h3>
|
||||
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
|
||||
</h3>
|
||||
|
||||
@@ -23,10 +23,10 @@ async function hasStrudelJson(subpath) {
|
||||
async function loadStrudelJson(subpath) {
|
||||
const contents = await readTextFile(subpath + '/strudel.json', { dir });
|
||||
const sampleMap = JSON.parse(contents);
|
||||
processSampleMap(sampleMap, (key, value) => {
|
||||
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value, fileResolver(subpath)), {
|
||||
processSampleMap(sampleMap, (key, bank) => {
|
||||
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank, fileResolver(subpath)), {
|
||||
type: 'sample',
|
||||
samples: value,
|
||||
samples: bank,
|
||||
fileSystem: true,
|
||||
tag: 'local',
|
||||
});
|
||||
|
||||
@@ -78,6 +78,7 @@ export function loadModules() {
|
||||
import('@strudel/codemirror'),
|
||||
import('@strudel/hydra'),
|
||||
import('@strudel/serial'),
|
||||
import('@strudel/shader'),
|
||||
import('@strudel/soundfonts'),
|
||||
import('@strudel/csound'),
|
||||
import('@strudel/tidal'),
|
||||
|
||||
@@ -30,7 +30,8 @@ export const defaultSettings = {
|
||||
isZen: false,
|
||||
soundsFilter: 'all',
|
||||
patternFilter: 'community',
|
||||
panelPosition: 'right',
|
||||
panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom',
|
||||
isPanelPinned: true,
|
||||
userPatterns: '{}',
|
||||
audioDeviceName: defaultAudioDeviceName,
|
||||
audioEngineTarget: audioEngineTargets.webaudio,
|
||||
@@ -60,6 +61,7 @@ export function useSettings() {
|
||||
return {
|
||||
...state,
|
||||
isZen: parseBoolean(state.isZen),
|
||||
isPanelPinned: parseBoolean(state.isPanelPinned),
|
||||
isBracketMatchingEnabled: parseBoolean(state.isBracketMatchingEnabled),
|
||||
isBracketClosingEnabled: parseBoolean(state.isBracketClosingEnabled),
|
||||
isLineNumbersDisplayed: parseBoolean(state.isLineNumbersDisplayed),
|
||||
@@ -77,6 +79,7 @@ export function useSettings() {
|
||||
}
|
||||
|
||||
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
||||
export const setPanelPinned = (isPinned) => settingsMap.setKey('isPanelPinned', isPinned);
|
||||
|
||||
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
}
|
||||
:root {
|
||||
--app-height: 100vh;
|
||||
--app-width: 100vw;
|
||||
}
|
||||
|
||||
#console-tab {
|
||||
|
||||
@@ -34,6 +34,7 @@ module.exports = {
|
||||
},
|
||||
spacing: {
|
||||
'app-height': 'var(--app-height)',
|
||||
'app-width': 'var(--app-width)',
|
||||
},
|
||||
typography(theme) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user