Compare commits

..

41 Commits

Author SHA1 Message Date
alex 85ee97c9ef minimini starting to work with some passing tests and highlights 2024-10-04 18:52:40 +01:00
alex 479041ebb6 paste in proto minimini 2024-10-04 16:00:36 +01:00
Felix Roos ad080d02b3 Merge pull request #1189 from kdiab/main
remove redundant example for cat, update snapshot
2024-09-27 00:17:00 +02:00
Khalid e9e7e02547 Fixed access to merged description in factories.mdx 2024-09-26 17:58:59 -04:00
Felix Roos 2ffc6c9601 Merge pull request #1185 from Bubobubobubobubo/search-bar
Adding search bar (soundtab.jsx)
2024-09-25 13:18:19 +02:00
Raphaël Forment 3a9349d9f5 format code for tests 2024-09-25 12:59:18 +02:00
Raphaël Forment bd3f2845f8 Reverting code 2024-09-25 12:57:20 +02:00
Alex McLean 2d320c459b Screenreader improvements (#1158)
* hide ascii art for screen readers
* aria-labels for minirepl buttons
2024-09-23 16:18:34 +01:00
Raphaël Forment ff9f4a8a0d Simplify search bar code 2024-09-22 23:40:27 +02:00
Raphaël Forment d7b94807cb Merge branch 'tidalcycles:main' into search-bar 2024-09-22 23:30:11 +02:00
kdiab 2c16a37a68 Combine examples for stack, match wording for cat, stack and seq 2024-09-22 10:42:46 -04:00
kdiab 717e4a2c1d Combine examples for seq and remove extra entry 2024-09-22 10:31:50 -04:00
kdiab c225b4719f Combine examples for cat 2024-09-21 11:01:00 -04:00
kdiab 25f762c393 remove redundant example for cat, update snapshot 2024-09-20 23:38:21 -04:00
Alex McLean b75561e3c7 Fix serial timing (#1188)
* fix serial timing
* format
2024-09-20 21:26:41 +01:00
Alex McLean abaab89061 Add bite function (#1187)
For patterning slices of patterns. ref #31.
2024-09-19 23:26:30 +01:00
Felix Roos e409086b34 Merge pull request #1147 from tidalcycles/bettern-zen-spacing
better spacing in zen mode
2024-09-14 13:30:53 +02:00
Felix Roos 532f1435d9 revert lockfile 2024-09-14 13:15:44 +02:00
Raphaël Forment 6cb61ade03 Adding search bar in soundtab.jsx 2024-09-14 11:39:59 +02:00
Felix Roos a40219ba06 Merge pull request #1039 from tidalcycles/filter
add filter + filterWhen + within
2024-09-14 10:50:23 +02:00
Felix Roos 9c99f5ada2 Merge pull request #1101 from tidalcycles/sampler-refactoring
refactor sampler
2024-09-14 10:49:21 +02:00
Felix Roos 2d72c8faa0 Merge pull request #1146 from tidalcycles/midi-error-msg
handle midin device not found error
2024-09-14 10:43:17 +02:00
Felix Roos b188fc6b6d Merge branch 'main' into sampler-refactoring 2024-09-14 10:38:27 +02:00
Martín Rodríguez bfb22228cc Add a search bar to the REPL Reference tab (#1165)
* Add a search bar to the REPL Reference tab
Simple text input that sticks to the top.
Filters by just checking if the search term is in the entry name or synonyms, if available.
2024-09-13 20:59:23 +01:00
Edward Betts 9714d88d61 Correct spelling mistakes (#1183) 2024-09-13 20:58:47 +01:00
Alex McLean bdddb91510 add seqPLoop, ref #31 (#1182) 2024-09-07 22:41:29 +01:00
Jade (Rose) Rowland 5bf1805534 Merge pull request #1180 from daslyfe/phaser_controls
make phaser control match superdirt
2024-09-04 23:52:50 -04:00
Jade (Rose) Rowland 202685e8c4 update test snapshot 2024-09-04 23:50:48 -04:00
Jade (Rose) Rowland aba633e16e fix 2024-09-04 23:42:38 -04:00
Jade (Rose) Rowland da45ce689c fixed formatting 2024-09-04 23:36:20 -04:00
Jade (Rose) Rowland acea7e435e Merge pull request #1179 from tidalcycles/revert-1178-consistent_phaser_controls
Revert "Make phaser control consistent with superdirt"
2024-09-04 23:33:41 -04:00
Jade (Rose) Rowland cb4810cbbc Revert "Make phaser control consistent with superdirt" 2024-09-04 23:32:35 -04:00
Jade (Rose) Rowland 5ec205b383 Merge pull request #1178 from daslyfe/consistent_phaser_controls
Make phaser control consistent with superdirt
2024-09-04 23:29:11 -04:00
Jade (Rose) Rowland ebc0b293a9 updated 2024-09-04 23:20:42 -04:00
Felix Roos dffeac41af Merge remote-tracking branch 'origin/main' into bettern-zen-spacing 2024-07-27 15:16:14 +02:00
Felix Roos 943d78f268 better spacing in zen mode 2024-07-25 16:44:02 +02:00
Felix Roos 5ac59e95dd handle midin device not found error 2024-07-25 16:07:58 +02:00
Felix Roos 69a5d62027 rename "samples" to "bank" 2024-05-17 09:42:03 +02:00
Felix Roos 226cf356d6 fix: lint 2024-05-16 11:05:38 +02:00
Felix Roos 73d49f152a refactor sampler:
- delegate getSampleBufferSource logic to getSampleInfo + getSampleBuffer
- move buffer logic up from onTriggerSample to those functions
2024-05-16 10:52:54 +02:00
Felix Roos cdba9efb59 add filter + filterWhen + within 2024-04-01 03:17:15 +02:00
23 changed files with 805 additions and 606 deletions
+5 -4
View File
@@ -454,9 +454,6 @@ export const { drive } = registerControl('drive');
*/
export const { channels, ch } = registerControl('channels', 'ch');
// superdirt only
export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
/**
* Phaser audio effect that approximates popular guitar pedals.
*
@@ -468,7 +465,11 @@ export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
* .phaser("<1 2 4 8>")
*
*/
export const { phaser, ph } = registerControl(['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph');
export const { phaserrate, ph, phaser } = registerControl(
['phaserrate', 'phaserdepth', 'phasercenter', 'phasersweep'],
'ph',
'phaser',
);
/**
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
+107 -31
View File
@@ -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
+6 -1
View File
@@ -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(
+173 -223
View File
@@ -1,253 +1,203 @@
/*
mini.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/mini/mini.mjs>
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/mini/mini.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as krill from './krill-parser.js';
import * as strudel from '@strudel/core';
import Fraction, { lcm } from '@strudel/core/fraction.mjs';
const randOffset = 0.0003;
const applyOptions = (parent, enter) => (pat, i) => {
const ast = parent.source_[i];
const options = ast.options_;
const ops = options?.ops;
const tactus_source = pat.__tactus_source;
if (ops) {
for (const op of ops) {
switch (op.type_) {
case 'stretch': {
const legalTypes = ['fast', 'slow'];
const { type, amount } = op.arguments_;
if (!legalTypes.includes(type)) {
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`);
}
pat = strudel.reify(pat)[type](enter(amount));
break;
}
case 'replicate': {
const { amount } = op.arguments_;
pat = strudel.reify(pat);
pat = pat._repeatCycles(amount)._fast(amount);
break;
}
case 'bjorklund': {
if (op.arguments_.rotation) {
pat = pat.euclidRot(enter(op.arguments_.pulse), enter(op.arguments_.step), enter(op.arguments_.rotation));
} else {
pat = pat.euclid(enter(op.arguments_.pulse), enter(op.arguments_.step));
}
break;
}
case 'degradeBy': {
pat = strudel
.reify(pat)
._degradeByWith(strudel.rand.early(randOffset * op.arguments_.seed), op.arguments_.amount ?? 0.5);
break;
}
case 'tail': {
const friend = enter(op.arguments_.element);
pat = pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend);
break;
}
case 'range': {
const friend = enter(op.arguments_.element);
pat = strudel.reify(pat);
const arrayRange = (start, stop, step = 1) =>
Array.from({ length: Math.abs(stop - start) / step + 1 }, (value, index) =>
start < stop ? start + index * step : start - index * step,
);
let range = (apat, bpat) => apat.squeezeBind((a) => bpat.bind((b) => strudel.fastcat(...arrayRange(a, b))));
pat = range(pat, friend);
break;
}
default: {
console.warn(`operator "${op.type_}" not implemented`);
}
}
}
}
pat.__tactus_source = pat.__tactus_source || tactus_source;
return pat;
// "parser"
let token_types = {
open_polyrhythm: /^\[/,
close_polyrhythm: /^\]/,
open_polymeter: /^\{/,
close_polymeter: /^\}/,
open_slowpolymeter: /^\</,
close_slowpolymeter: /^\>/,
subpat_delimiter: /^,/,
fast: /^\*/,
slow: /^\//,
// is there a better way without back-tracking for whole numbers?
// float: /^(\d*\.)?\d+/,
plain: /^[a-zA-Z0-9\.\#]+/,
};
// expects ast from mini2ast + quoted mini string + optional callback when a node is entered
export function patternifyAST(ast, code, onEnter, offset = 0) {
onEnter?.(ast);
const enter = (node) => patternifyAST(node, code, onEnter, offset);
switch (ast.type_) {
case 'pattern': {
// resolveReplications(ast);
const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter));
const alignment = ast.arguments_.alignment;
const with_tactus = children.filter((child) => child.__tactus_source);
let pat;
switch (alignment) {
case 'stack': {
pat = strudel.stack(...children);
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
case 'polymeter_slowcat': {
pat = strudel.stack(...children.map((child) => child._slow(child.__weight)));
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
case 'polymeter': {
// polymeter
const stepsPerCycle = ast.arguments_.stepsPerCycle
? enter(ast.arguments_.stepsPerCycle).fmap((x) => strudel.Fraction(x))
: strudel.pure(strudel.Fraction(children.length > 0 ? children[0].__weight : 1));
let token_offset;
function next_token(code, global_offset) {
for (let type in token_types) {
const match = code.match(token_types[type]);
if (match) {
const leftpad = match[0].length - match[0].trimLeft().length;
const rightpad = match[0].length - match[0].trimRight().length;
const result = {
type,
value: match[0],
location: [token_offset + leftpad + global_offset, token_offset + match[0].length + global_offset - rightpad],
};
token_offset += match[0].length;
return result;
}
}
throw new Error('could not match "' + code + '"');
}
const aligned = children.map((child) => child.fast(stepsPerCycle.fmap((x) => x.div(child.__weight))));
pat = strudel.stack(...aligned);
break;
}
case 'rand': {
pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
case 'feet': {
pat = strudel.fastcat(...children);
break;
}
default: {
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (weightedChildren) {
const weightSum = ast.source_.reduce(
(sum, child) => sum.add(child.options_?.weight || strudel.Fraction(1)),
strudel.Fraction(0),
);
pat = strudel.timeCat(
...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]),
);
pat.__weight = weightSum; // for polymeter
pat.tactus = weightSum;
if (with_tactus.length) {
pat.tactus = pat.tactus.mul(lcm(...with_tactus.map((x) => Fraction(x.tactus))));
}
} else {
pat = strudel.sequence(...children);
pat.tactus = children.length;
}
if (ast.arguments_.tactus) {
pat.__tactus_source = true;
}
}
}
if (with_tactus.length) {
pat.__tactus_source = true;
}
return pat;
function tokenize(code, global_offset) {
let tokens = [];
token_offset = 0;
while (code.length > 0) {
const trimmed = code.trim();
token_offset += code.length - trimmed.length;
code = trimmed;
const token = next_token(code, global_offset);
code = code.slice(token.value.length);
tokens.push(token);
}
return tokens;
}
class Parser {
parse(code, global_offset) {
this.tokens = tokenize(code, global_offset);
const subpats = this.parse_subpats();
return { type: 'polymeter', subpats };
}
consume(type) {
const token = this.tokens.shift();
this.offset += token.length;
if (token.type !== type) {
throw new Error('expected token type ' + type + ' got ' + token.type);
}
case 'element': {
1;
return enter(ast.source_);
return token;
}
parse_step() {
let next = this.tokens[0]?.type;
if (next === 'open_polyrhythm') {
return this.parse_polyrhythm();
}
case 'atom': {
if (ast.source_ === '~' || ast.source_ === '-') {
return strudel.silence;
}
if (!ast.location_) {
console.warn('no location for', ast);
return ast.source_;
}
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
if (offset === -1) {
// skip location handling (used when getting leaves to avoid confusion)
return strudel.pure(value);
}
const [from, to] = getLeafLocation(code, ast, offset);
return strudel.pure(value).withLoc(from, to);
if (next === 'open_polymeter') {
return this.parse_polymeter();
}
case 'stretch':
return enter(ast.source_).slow(enter(ast.arguments_.amount));
default:
console.warn(`node type "${ast.type_}" not implemented -> returning silence`);
return strudel.silence;
if (next === 'open_slowpolymeter') {
return this.parse_slowpolymeter();
}
if (next === 'plain') {
return this.consume('plain');
}
throw new Error('unexpected token ' + this.tokens[0]?.value + ' of type ' + this.tokens[0]?.type);
}
parse_expr() {
let result = this.parse_step();
let looking = true;
while (looking) {
let next = this.tokens[0]?.type;
if (!next) {
looking = false;
} else {
switch (next) {
case 'fast':
case 'slow':
this.consume(next);
const factor = this.parse_step();
result = { type: next, factor, step: result };
break;
default:
looking = false;
}
}
}
return result;
}
parse_seq(...close_types) {
const steps = [];
while (!close_types.includes(this.tokens[0]?.type)) {
steps.push(this.parse_expr());
}
return { type: 'seq', steps };
}
parse_subpats(close_type) {
const subpats = [];
while (true) {
subpats.push(this.parse_seq(close_type, 'subpat_delimiter'));
const next = this.tokens[0]?.type;
if (next) {
this.consume(next);
}
if (next === close_type) {
break;
}
}
return subpats;
}
parse_polymeter() {
this.consume('open_polymeter');
const subpats = this.parse_subpats('close_polymeter');
return { type: 'polymeter', subpats };
}
parse_polyrhythm() {
this.consume('open_polyrhythm');
const subpats = this.parse_subpats('close_polyrhythm');
return { type: 'polyrhythm', subpats };
}
parse_slowpolymeter() {
this.consume('open_slowpolymeter');
const subpats = this.parse_subpats('close_slowpolymeter');
return { type: 'slowpolymeter', subpats };
}
}
// takes quoted mini string + leaf node within, returns source location of node (whitespace corrected)
export const getLeafLocation = (code, leaf, globalOffset = 0) => {
// value is expected without quotes!
const { start, end } = leaf.location_;
const actual = code?.split('').slice(start.offset, end.offset).join('');
// make sure whitespaces are not part of the highlight
const [offsetStart = 0, offsetEnd = 0] = actual
? actual.split(leaf.source_).map((p) => p.split('').filter((c) => c === ' ').length)
: [];
return [start.offset + offsetStart + globalOffset, end.offset - offsetEnd + globalOffset];
};
// takes quoted mini string, returns ast
export const mini2ast = (code, start = 0, userCode = code) => {
try {
return krill.parse(code);
} catch (error) {
const region = [error.location.start.offset + start, error.location.end.offset + start];
const line = userCode.slice(0, region[0]).split('\n').length;
throw new Error(`[mini] parse error at line ${line}: ${error.message}`);
function patternifyTree(tree) {
if (tree.type === 'polyrhythm') {
const args = tree.subpats.map((arg) => patternifyTree(arg));
return strudel.polyrhythm(...args);
}
if (tree.type === 'slowpolymeter') {
const args = tree.subpats.map((arg) => patternifyTree(arg));
return strudel.s_polymeterSteps(1, ...args);
}
if (tree.type === 'polymeter') {
const args = tree.subpats.map((subpat) => patternifyTree(subpat));
return strudel.s_polymeter(...args);
}
if (tree.type === 'fast') {
const step = patternifyTree(tree.step);
const factor = patternifyTree(tree.factor);
return strudel.fast(factor, step);
}
if (tree.type === 'slow') {
const step = patternifyTree(tree.step);
const factor = patternifyTree(tree.factor);
return strudel.slow(factor, step);
}
if (tree.type === 'plain') {
return strudel.pure(tree.value).withLoc(tree.location[0], tree.location[1]);
}
if (tree.type === 'seq') {
const steps = tree.steps.map((step) => patternifyTree(step));
return strudel.fastcat(...steps);
}
}
const parser = new Parser();
export const m = (code, global_offset) => {
const tree = parser.parse(code, global_offset + 1);
const pat = patternifyTree(tree);
return strudel.reify(pat);
};
// takes quoted mini string, returns all nodes that are leaves
export const getLeaves = (code, start, userCode) => {
const ast = mini2ast(code, start, userCode);
let leaves = [];
patternifyAST(
ast,
code,
(node) => {
if (node.type_ === 'atom') {
leaves.push(node);
}
},
-1,
);
return leaves;
};
// takes quoted mini string, returns locations [fromCol,toCol] of all leaf nodes
export const getLeafLocations = (code, start = 0, userCode) => {
return getLeaves(code, start, userCode).map((l) => getLeafLocation(code, l, start));
export const getLeafLocations = (code, start) => {
code = code.replace(/^"/, '');
code = code.replace(/"$/, '');
const leaves = tokenize(code, start).filter((x) => x.type == 'plain');
return leaves.map((x) => x.location.map((y) => y + 1));
};
// mini notation only (wraps in "")
export const mini = (...strings) => {
const pats = strings.map((str) => {
const code = `"${str}"`;
const ast = mini2ast(code);
return patternifyAST(ast, code);
});
const pats = strings.map(m);
return strudel.sequence(...pats);
};
// turns str mini string (without quotes) into pattern
// offset is the position of the mini string in the JS code
// each leaf node will get .withLoc added
// this function is used by the transpiler for double quoted strings
export const m = (str, offset) => {
const code = `"${str}"`;
const ast = mini2ast(code);
return patternifyAST(ast, code, null, offset);
};
// includes haskell style (raw krill parsing)
export const h = (string) => {
const ast = mini2ast(string);
return patternifyAST(ast, string);
};
export function minify(thing) {
if (typeof thing === 'string') {
return mini(thing);
+1 -14
View File
@@ -221,19 +221,6 @@ describe('mini', () => {
});
});
describe('getLeafLocation', () => {
it('gets location of leaf nodes', () => {
const code = '"bd sd"';
const ast = mini2ast(code);
const bd = ast.source_[0].source_;
expect(getLeafLocation(code, bd)).toEqual([1, 3]);
const sd = ast.source_[1].source_;
expect(getLeafLocation(code, sd)).toEqual([4, 6]);
});
});
describe('getLeafLocations', () => {
it('gets locations of leaf nodes', () => {
expect(getLeafLocations('"bd sd"')).toEqual([
@@ -242,9 +229,9 @@ describe('getLeafLocations', () => {
]);
expect(getLeafLocations('"bd*2 [sd cp]"')).toEqual([
[1, 3], // bd columns
[4, 5], // "2" columns
[7, 9], // sd columns
[10, 12], // cp columns
[4, 5], // "2" columns
]);
expect(getLeafLocations('"bd*<2 3>"')).toEqual([
[1, 3], // bd columns
+2 -2
View File
@@ -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);
+61 -48
View File
@@ -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;
+2 -9
View File
@@ -147,14 +147,6 @@ export function initializeAudioOutput() {
destinationGain.connect(audioContext.destination);
}
export function setGlobalGain(gain) {
if (destinationGain == null) {
initializeAudioOutput();
}
destinationGain.gain.value = gain;
}
// input: AudioNode, channels: ?Array<int>
export const connectToDestination = (input, channels = [0, 1]) => {
const ctx = getAudioContext();
@@ -383,7 +375,7 @@ export const superdough = async (value, t, hapDuration) => {
bandq = getDefaultValue('bandq'),
channels = getDefaultValue('channels'),
//phaser
phaser,
phaserrate: phaser,
phaserdepth = getDefaultValue('phaserdepth'),
phasersweep,
phasercenter,
@@ -429,6 +421,7 @@ export const superdough = async (value, t, hapDuration) => {
};
if (bank && s) {
s = `${bank}_${s}`;
value.s = s;
}
// get source AudioNode
+383 -176
View File
@@ -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 ]",
@@ -4937,149 +5124,149 @@ exports[`runs examples > example "perlin" example index 0 1`] = `
exports[`runs examples > example "phaser" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:1 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:1 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:1 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:1 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:4 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:4 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:4 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:4 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:4 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:4 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:4 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:4 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:8 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:8 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:8 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:8 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:8 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:8 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:8 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:8 ]",
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaserrate:1 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaserrate:4 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaserrate:8 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaserrate:8 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaserrate:8 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaserrate:8 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaserrate:8 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaserrate:8 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaserrate:8 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaserrate:8 ]",
]
`;
exports[`runs examples > example "phasercenter" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasercenter:2000 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasercenter:4000 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasercenter:800 ]",
]
`;
exports[`runs examples > example "phaserdepth" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.5 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:0.75 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phaserdepth:1 ]",
]
`;
exports[`runs examples > example "phasersweep" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasersweep:2000 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasersweep:4000 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaserrate:2 phasersweep:800 ]",
]
`;
@@ -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 ]",
@@ -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
+4
View File
@@ -120,6 +120,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 +130,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 +142,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 +153,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" />
-12
View File
@@ -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} />
+1 -1
View File
@@ -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);
@@ -3,7 +3,7 @@ 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>{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
<pre aria-hidden="true">{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
██╔════╝╚══██╔══╝██╔══██╗██║ ██║██╔══██╗██╔════╝██║
███████╗ ██║ ██████╔╝██║ ██║██║ ██║█████╗ ██║
╚════██║ ██║ ██╔══██╗██║ ██║██║ ██║██╔══╝ ██║
@@ -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,9 +12,29 @@ 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">
<div class="w-full ml-2 mb-2 top-0 sticky">
<input
className="w-full p-1 bg-background rounded-md"
placeholder="Search"
value={search}
onInput={(event) => setSearch(event.target.value)}
/>
</div>
{visibleFunctions.map((entry, i) => (
<a
key={i}
@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { audioEngineTargets, defaultSettings, settingsMap, useSettings } from '../../../settings.mjs';
import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs';
import { themes } from '@strudel/codemirror';
import { isUdels, setGlobalAudioVolume } from '../../util.mjs';
import { isUdels } from '../../util.mjs';
import { ButtonGroup } from './Forms.jsx';
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
@@ -32,47 +31,23 @@ function SelectInput({ value, options, onChange }) {
);
}
function NumberSlider({ value, onChange, min, max, step = 1, ...rest }) {
const fractionalDigits = useMemo(() => {
const stepStr = step.toString();
const decimalPointIdx = stepStr.indexOf('.');
if (decimalPointIdx < 0) {
return 0;
}
return stepStr.slice(decimalPointIdx + 1).length;
}, [step]);
const textInputCharWidth = useMemo(() => {
const maxValueWholePartLength = Math.floor(max).toString().length;
return maxValueWholePartLength + '.'.length + fractionalDigits;
}, [max, fractionalDigits]);
function NumberSlider({ value, onChange, step = 1, ...rest }) {
return (
<div className="flex space-x-2 gap-1">
<input
className="p-2 grow"
type="range"
value={value}
min={min}
max={max}
step={step}
onChange={(e) => onChange(Number(e.target.value))}
{...rest}
/>
<input
type="number"
style={{
// approximate text size + some leeway for the default padding + some space between the browser's up/down arrows and the input's value
width: `calc(${textInputCharWidth}ch + 2 * 0.75rem + 1rem)`,
}}
value={Number(value).toFixed(fractionalDigits)}
min={min}
max={max}
value={value}
step={step}
className="bg-background rounded-md"
className="w-16 bg-background rounded-md"
onChange={(e) => onChange(Number(e.target.value))}
{...rest}
/>
</div>
);
@@ -126,7 +101,6 @@ export function SettingsTab({ started }) {
panelPosition,
audioDeviceName,
audioEngineTarget,
audioVolume,
} = useSettings();
const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@@ -161,19 +135,6 @@ export function SettingsTab({ started }) {
}}
/>
</FormItem>
<FormItem label="Audio Volume">
{audioEngineTarget === audioEngineTargets.osc && (
<span class="text-sm italic">Has no effect when Audio Engine Target is OSC</span>
)}
<NumberSlider
value={audioVolume}
onChange={(audioVolume) => settingsMap.setKey('audioVolume', audioVolume)}
min={0}
max={100}
step={0.1}
disabled={audioEngineTarget === audioEngineTargets.osc}
/>
</FormItem>
<FormItem label="Theme">
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
</FormItem>
@@ -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 React, { 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,17 @@ 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">
<div className="w-full ml-2 mb-2 top-0 sticky">
<input
className="w-full p-1 bg-background rounded-md"
placeholder="Search"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
<div className="pb-2 flex shrink-0 flex-wrap">
<ButtonGroup
value={soundsFilter}
+3 -3
View File
@@ -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',
});
+1 -5
View File
@@ -14,7 +14,7 @@ import {
resetLoadedSounds,
initAudioOnFirstClick,
} from '@strudel/webaudio';
import { getAudioDevices, setAudioDevice, setGlobalAudioVolume, setVersionDefaultsFrom } from './util.mjs';
import { getAudioDevices, setAudioDevice, setVersionDefaultsFrom } from './util.mjs';
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
import { clearHydra } from '@strudel/hydra';
import { useCallback, useEffect, useRef, useState } from 'react';
@@ -162,7 +162,6 @@ export function useReplContext() {
// on first load, set stored audio device if possible
useEffect(() => {
const { audioDeviceName } = _settings;
if (audioDeviceName !== defaultAudioDeviceName) {
getAudioDevices().then((devices) => {
const deviceID = devices.get(audioDeviceName);
@@ -174,9 +173,6 @@ export function useReplContext() {
}
}, []);
// set stored audio volume
useEffect(() => setGlobalAudioVolume(_settings.audioVolume), [_settings.audioVolume]);
//
// UI Actions
//
+1 -20
View File
@@ -1,12 +1,6 @@
import { evalScope, hash2code, logger } from '@strudel/core';
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
import {
getAudioContext,
initializeAudioOutput,
setDefaultAudioContext,
setGlobalGain,
setVersionDefaults,
} from '@strudel/webaudio';
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext, setVersionDefaults } from '@strudel/webaudio';
import { getMetadata } from '../metadata_parser';
import { isTauri } from '../tauri.mjs';
import './Repl.css';
@@ -194,19 +188,6 @@ export const setAudioDevice = async (id) => {
initializeAudioOutput();
};
const NATURAL_LOG_10 = Math.log(1000);
const LOW_VOLUME_CONSTANT = Math.exp(0.1 * NATURAL_LOG_10);
export const setGlobalAudioVolume = (volume) => {
// Gain is calculated to adjust the volume to a logarithmic scale to match how us humans perceive loudness.
// Formula is taken from https://www.dr-lex.be/info-stuff/volumecontrols.html
volume /= 100; // [0, 1]
let gain = volume >= 0.1 ? 0.001 * Math.exp(NATURAL_LOG_10 * volume) : volume * 10 * 0.001 * LOW_VOLUME_CONSTANT;
gain = Math.max(0, Math.min(gain, 1)); // just in case
setGlobalGain(gain);
};
export function setVersionDefaultsFrom(code) {
try {
const metadata = getMetadata(code);
-1
View File
@@ -34,7 +34,6 @@ export const defaultSettings = {
userPatterns: '{}',
audioDeviceName: defaultAudioDeviceName,
audioEngineTarget: audioEngineTargets.webaudio,
audioVolume: 50,
};
let search = null;