mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 05:05:26 -04:00
Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89acc4870b | |||
| 314c739c3a | |||
| 30b18fc997 | |||
| 168ac60853 | |||
| dff0c0c3b0 | |||
| ea3e9de7ab | |||
| c93fd900bc | |||
| a761aa9a37 | |||
| f37eb92d93 | |||
| 2c7cc5d325 | |||
| 16659c82d2 | |||
| 4ed3ca4128 | |||
| 9b2899c8fb | |||
| fada7fc9cc | |||
| 2be5bca42b | |||
| b1ebf6367c | |||
| 57e373f578 | |||
| 5c01874a32 | |||
| dd89e11633 | |||
| 4e51f19780 | |||
| 33d37d8dee | |||
| 60ce34af43 | |||
| 2602ff6e34 | |||
| ef5cd36a37 | |||
| 7d47f5aba3 | |||
| 33cb506c71 | |||
| 5fb36acb04 | |||
| 5f985f359b | |||
| 09045d5387 | |||
| 371af755df | |||
| 0485632e22 | |||
| c3d6de2412 | |||
| 36d49b11d9 | |||
| 866277510e | |||
| da8e00eb4e | |||
| cd30b2af3f | |||
| 38b3337d35 | |||
| 84d34aa12b | |||
| 96b7b68900 | |||
| 2a6ffbf5a4 | |||
| 1ac99e260b | |||
| 519062c8d0 | |||
| 78d7cdf662 | |||
| a191b1e811 | |||
| b372161874 | |||
| 5bbec7b17c | |||
| 54b847a8ef | |||
| a40a0f730f | |||
| a677b94297 | |||
| d102528d0a | |||
| 854692f975 | |||
| bdc321df4d | |||
| 230e8e9d4d | |||
| 96c49689ec | |||
| 698f560d56 | |||
| 67ee493e56 | |||
| 4fc7ef698a | |||
| 89434fc3bd | |||
| 6129f9afe8 | |||
| e5f38dbbc6 | |||
| 5534db95bc | |||
| 37c45424fd | |||
| ed44f4ed0c | |||
| f829f35cfa | |||
| 3033acadbb | |||
| 5c2a8a68c0 | |||
| e9544f8f92 | |||
| 1a6743dba9 | |||
| 06d6621c14 | |||
| d7b82edae3 | |||
| 5eae156134 | |||
| d8843e6057 | |||
| cf7c734778 | |||
| 240a8930ef | |||
| 4f3046593a | |||
| a0bd5bdd35 | |||
| e3bc63e4fa | |||
| fbf7c6293f | |||
| e128b02da1 | |||
| 2168dbc6fc | |||
| 97db895354 | |||
| 48fc68bfdb | |||
| 3741ffbb59 | |||
| caf58492f2 | |||
| 0c5bdd0205 | |||
| ae6da0d6ef | |||
| bdbb4cb495 | |||
| 566c358e8d | |||
| 324cffe6df | |||
| dadb8119e8 | |||
| 335d54b5f4 | |||
| 0fd851999f | |||
| 50f88798d8 | |||
| 8033cfc49d | |||
| 882f450ee8 | |||
| 4d60a582fe | |||
| 7ef3d33d0e | |||
| 24328ee8ae | |||
| d45c380389 | |||
| fbb3d144a1 | |||
| 7e7e299bce | |||
| e715e2ba2f | |||
| 63c6a9735c | |||
| 0c1529237c | |||
| 4f8a2a0900 | |||
| 9a2a26e904 | |||
| 067c3433b6 | |||
| d0b07c4890 | |||
| 24f8f48a2f | |||
| acafcc9888 | |||
| ec4cfe6f86 | |||
| 7b0c80c8be | |||
| 29029714ee | |||
| cd1203d1e2 | |||
| 4dfc34515c | |||
| 11a9f3ced3 | |||
| 5be1a7000c | |||
| 9059c1b850 | |||
| ec60fe42be | |||
| 275931969e | |||
| e37e0f189a | |||
| 8103943396 | |||
| 9497c99e9e | |||
| 2172d1a767 | |||
| e72c47869a | |||
| 5ea2124e91 | |||
| 0c08919bfa | |||
| a7562c3803 | |||
| 5f4d8f6862 | |||
| 9c7d0951b7 | |||
| 75e57c75cf | |||
| 04502b05d4 | |||
| e3e3a8404a | |||
| 48bf17614f | |||
| 1941b1ff65 | |||
| 46aa3d275a | |||
| d667e790b9 | |||
| bc345b569b | |||
| ee84b4e013 | |||
| e77129fa51 | |||
| 591855919e | |||
| 7af6188a8a | |||
| 1cd31cfce9 |
@@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16, 17]
|
node-version: [18]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
Generated
+3212
-3174
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -4,6 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"description": "Port of tidalcycles to javascript",
|
"description": "Port of tidalcycles to javascript",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"pretest": "cd tutorial && npm run jsdoc-json",
|
||||||
"test": "vitest run --version",
|
"test": "vitest run --version",
|
||||||
"test-ui": "vitest --ui",
|
"test-ui": "vitest --ui",
|
||||||
"test-coverage": "vitest --coverage",
|
"test-coverage": "vitest --coverage",
|
||||||
@@ -43,7 +44,6 @@
|
|||||||
"c8": "^7.12.0",
|
"c8": "^7.12.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"gh-pages": "^4.0.0",
|
"gh-pages": "^4.0.0",
|
||||||
"happy-dom": "^6.0.4",
|
|
||||||
"jsdoc": "^3.6.10",
|
"jsdoc": "^3.6.10",
|
||||||
"jsdoc-json": "^2.0.2",
|
"jsdoc-json": "^2.0.2",
|
||||||
"jsdoc-to-markdown": "^7.1.1",
|
"jsdoc-to-markdown": "^7.1.1",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
examples
|
||||||
@@ -40,11 +40,6 @@ const generic_params = [
|
|||||||
* @example
|
* @example
|
||||||
* n("0 1 2 3").s('east').osc()
|
* n("0 1 2 3").s('east').osc()
|
||||||
*/
|
*/
|
||||||
// TODO: nOut does not work
|
|
||||||
// TODO: notes don't work as expected
|
|
||||||
// current "workaround" for notes:
|
|
||||||
// s('superpiano').n("<c0 d0 e0 g0>"._asNumber()).osc()
|
|
||||||
// -> .n or .osc (or .superdirt) would need to convert note strings to numbers
|
|
||||||
// also see https://github.com/tidalcycles/strudel/pull/63
|
// also see https://github.com/tidalcycles/strudel/pull/63
|
||||||
['f', 'n', 'The note or sample number to choose for a synth or sampleset'],
|
['f', 'n', 'The note or sample number to choose for a synth or sampleset'],
|
||||||
['f', 'note', 'The note or pitch to play a sound or synth with'],
|
['f', 'note', 'The note or pitch to play a sound or synth with'],
|
||||||
@@ -100,6 +95,18 @@ const generic_params = [
|
|||||||
'attack',
|
'attack',
|
||||||
'a pattern of numbers to specify the attack time (in seconds) of an envelope applied to each sample.',
|
'a pattern of numbers to specify the attack time (in seconds) of an envelope applied to each sample.',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
||||||
|
*
|
||||||
|
* @name bank
|
||||||
|
* @param {string | Pattern} bank the name of the bank
|
||||||
|
* @example
|
||||||
|
* s("bd sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['f', 'bank', 'selects sound bank to use'],
|
||||||
|
|
||||||
// TODO: find out how this works?
|
// TODO: find out how this works?
|
||||||
/*
|
/*
|
||||||
* Envelope decay time = the time it takes after the attack time to reach the sustain level.
|
* Envelope decay time = the time it takes after the attack time to reach the sustain level.
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
cyclist.mjs - <short description TODO>
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/cyclist.mjs>
|
||||||
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import createClock from './zyklus.mjs';
|
||||||
|
|
||||||
|
export class Cyclist {
|
||||||
|
worker;
|
||||||
|
pattern;
|
||||||
|
started = false;
|
||||||
|
cps = 1; // TODO
|
||||||
|
getTime;
|
||||||
|
phase = 0;
|
||||||
|
constructor({ interval, onTrigger, onError, getTime, latency = 0.1 }) {
|
||||||
|
this.getTime = getTime;
|
||||||
|
const round = (x) => Math.round(x * 1000) / 1000;
|
||||||
|
this.clock = createClock(
|
||||||
|
getTime,
|
||||||
|
(phase, duration, tick) => {
|
||||||
|
if (tick === 0) {
|
||||||
|
this.origin = phase;
|
||||||
|
}
|
||||||
|
const begin = round(phase - this.origin);
|
||||||
|
this.phase = begin - latency;
|
||||||
|
const end = round(begin + duration);
|
||||||
|
const time = getTime();
|
||||||
|
try {
|
||||||
|
const haps = this.pattern.queryArc(begin, end); // get Haps
|
||||||
|
// console.log('haps', haps.map((hap) => hap.value.n).join(' '));
|
||||||
|
haps.forEach((hap) => {
|
||||||
|
// console.log('hap', hap.value.n, hap.part.begin);
|
||||||
|
if (hap.part.begin.equals(hap.whole.begin)) {
|
||||||
|
const deadline = hap.whole.begin + this.origin - time + latency;
|
||||||
|
const duration = hap.duration * 1;
|
||||||
|
onTrigger?.(hap, deadline, duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('scheduler error', e);
|
||||||
|
onError?.(e);
|
||||||
|
}
|
||||||
|
}, // called slightly before each cycle
|
||||||
|
interval, // duration of each cycle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
getPhase() {
|
||||||
|
return this.phase;
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
if (!this.pattern) {
|
||||||
|
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
||||||
|
}
|
||||||
|
this.clock.start();
|
||||||
|
this.started = true;
|
||||||
|
}
|
||||||
|
pause() {
|
||||||
|
this.clock.stop();
|
||||||
|
delete this.origin;
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
delete this.origin;
|
||||||
|
this.clock.stop();
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
setPattern(pat, autostart = false) {
|
||||||
|
this.pattern = pat;
|
||||||
|
if (autostart && !this.started) {
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCps(cps = 1) {
|
||||||
|
this.cps = cps;
|
||||||
|
}
|
||||||
|
log(begin, end, haps) {
|
||||||
|
const onsets = haps.filter((h) => h.hasOnset());
|
||||||
|
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import Fraction, { gcd } from './fraction.mjs';
|
|||||||
* @example
|
* @example
|
||||||
* const line = drawLine("0 [1 2 3]", 10); // |0--123|0--123
|
* const line = drawLine("0 [1 2 3]", 10); // |0--123|0--123
|
||||||
* console.log(line);
|
* console.log(line);
|
||||||
|
* silence;
|
||||||
*/
|
*/
|
||||||
function drawLine(pat, chars = 60) {
|
function drawLine(pat, chars = 60) {
|
||||||
let cycle = 0;
|
let cycle = 0;
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
evaluate.mjs - <short description TODO>
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/eval/evaluate.mjs>
|
||||||
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as strudel from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
const { isPattern, Pattern } = strudel;
|
||||||
|
|
||||||
|
let scoped = false;
|
||||||
|
export const evalScope = async (...args) => {
|
||||||
|
if (scoped) {
|
||||||
|
console.warn('evalScope was called more than once.');
|
||||||
|
}
|
||||||
|
scoped = true;
|
||||||
|
const results = await Promise.allSettled(args);
|
||||||
|
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||||
|
results.forEach((result, i) => {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
|
||||||
|
};
|
||||||
|
|
||||||
|
function safeEval(str, options = {}) {
|
||||||
|
const { wrapExpression = true, wrapAsync = true } = options;
|
||||||
|
if (wrapExpression) {
|
||||||
|
str = `{${str}}`;
|
||||||
|
}
|
||||||
|
if (wrapAsync) {
|
||||||
|
str = `(async ()=>${str})()`;
|
||||||
|
}
|
||||||
|
const body = `"use strict";return (${str})`;
|
||||||
|
return Function(body)();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const evaluate = async (code, transpiler) => {
|
||||||
|
if (!scoped) {
|
||||||
|
await evalScope(); // at least scope Pattern.prototype.boostrap
|
||||||
|
}
|
||||||
|
if (transpiler) {
|
||||||
|
code = transpiler(code); // transform syntactically correct js code to semantically usable code
|
||||||
|
}
|
||||||
|
// if no transpiler is given, we expect a single instruction (!wrapExpression)
|
||||||
|
const options = { wrapExpression: !!transpiler };
|
||||||
|
let evaluated = await safeEval(code, options);
|
||||||
|
if (!isPattern(evaluated)) {
|
||||||
|
console.log('evaluated', evaluated);
|
||||||
|
const message = `got "${typeof evaluated}" instead of pattern`;
|
||||||
|
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
||||||
|
}
|
||||||
|
return { mode: 'javascript', pattern: evaluated };
|
||||||
|
};
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<div style="position: absolute; bottom: 0; right: 0; padding: 4px; width: 100vw; display: flex">
|
||||||
|
<button id="start" style="font-size: 2em">start</button>
|
||||||
|
<button id="stop" style="font-size: 2em">stop</button>
|
||||||
|
<button id="slower" style="font-size: 2em">slower</button>
|
||||||
|
<button id="faster" style="font-size: 2em">faster</button>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
style="font-size: 2em; background: #052b49; color: #fff; height: 100%; width: 100%; outline: none; border: 0"
|
||||||
|
id="text"
|
||||||
|
spellcheck="false"
|
||||||
|
>
|
||||||
|
Loading...</textarea
|
||||||
|
>
|
||||||
|
<script type="module">
|
||||||
|
document.body.style = 'margin: 0';
|
||||||
|
import * as strudel from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
const { cat, State, TimeSpan, Scheduler, getPlayableNoteValue, getFreq } = strudel;
|
||||||
|
Object.assign(window, strudel); // add strudel to eval scope
|
||||||
|
|
||||||
|
const ctx = new AudioContext();
|
||||||
|
const scheduler = new Scheduler({
|
||||||
|
// audioContext: getAudioContext(),
|
||||||
|
interval: 0.1,
|
||||||
|
onTrigger: (hap, time, duration) => {
|
||||||
|
const freq = getFrequency(hap);
|
||||||
|
const osc = ctx.createOscillator();
|
||||||
|
const gain = 0.2;
|
||||||
|
osc.frequency.value = freq;
|
||||||
|
osc.type = 'triangle';
|
||||||
|
const onset = ctx.currentTime + time;
|
||||||
|
const offset = onset + duration;
|
||||||
|
const attack = 0.05;
|
||||||
|
const release = 0.05;
|
||||||
|
osc.start(onset);
|
||||||
|
osc.stop(offset + release);
|
||||||
|
|
||||||
|
const g = ctx.createGain();
|
||||||
|
g.gain.setValueAtTime(gain, onset);
|
||||||
|
g.gain.linearRampToValueAtTime(gain, onset + attack);
|
||||||
|
g.gain.setValueAtTime(gain, offset - release);
|
||||||
|
g.gain.linearRampToValueAtTime(0, offset);
|
||||||
|
osc.connect(g);
|
||||||
|
g.connect(ctx.destination);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let initialCode = `stack('c4','e4',cat('g4','a4','b4','a4'))
|
||||||
|
.add(cat(0,1,2,3,4,3,2,1).slow(8))
|
||||||
|
.fast(2)
|
||||||
|
.cps(tri.range(1,8).slow(32))`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const base64 = decodeURIComponent(window.location.href.split('#')[1]);
|
||||||
|
initialCode = atob(base64);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('failed to decode', err);
|
||||||
|
}
|
||||||
|
const input = document.getElementById('text');
|
||||||
|
input.value = initialCode;
|
||||||
|
const evaluate = () => {
|
||||||
|
try {
|
||||||
|
const pattern = eval(input.value);
|
||||||
|
scheduler.setPattern(pattern);
|
||||||
|
window.location.hash = '#' + encodeURIComponent(btoa(input.value)); // update url hash
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
evaluate();
|
||||||
|
input.addEventListener('input', () => evaluate());
|
||||||
|
document.getElementById('start').addEventListener('click', async () => {
|
||||||
|
await ctx.resume();
|
||||||
|
scheduler.start();
|
||||||
|
});
|
||||||
|
document.getElementById('stop').addEventListener('click', () => scheduler.stop());
|
||||||
|
document.getElementById('slower').addEventListener('click', () => scheduler.setCps(scheduler.cps - 0.1));
|
||||||
|
document.getElementById('faster').addEventListener('click', () => scheduler.setCps(scheduler.cps + 0.1));
|
||||||
|
</script>
|
||||||
|
<!--
|
||||||
|
sequence(1,2).mul(55/2) // frequencies
|
||||||
|
.mul(slowcat(1,2))
|
||||||
|
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
|
||||||
|
.fast(3)
|
||||||
|
.freq()
|
||||||
|
.velocity(.5)
|
||||||
|
.s('sawtooth')
|
||||||
|
.cutoff(800)
|
||||||
|
.out()
|
||||||
|
-->
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="text"
|
||||||
|
value="seq('c3','eb3','g3').note().s('sawtooth').out()"
|
||||||
|
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
|
||||||
|
spellcheck="false"
|
||||||
|
/>
|
||||||
|
<button id="start">play</button>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script type="module">
|
||||||
|
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest');
|
||||||
|
|
||||||
|
const controls = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest/controls.mjs');
|
||||||
|
const { getAudioContext, Scheduler } = await import('https://cdn.skypack.dev/@strudel.cycles/webaudio@latest');
|
||||||
|
let scheduler;
|
||||||
|
|
||||||
|
const audioContext = getAudioContext();
|
||||||
|
const latency = 0.2;
|
||||||
|
|
||||||
|
Object.assign(window, strudel);
|
||||||
|
Object.assign(window, controls.default);
|
||||||
|
scheduler = new Scheduler({
|
||||||
|
audioContext,
|
||||||
|
interval: 0.1,
|
||||||
|
latency,
|
||||||
|
onEvent: (hap) => {
|
||||||
|
if (!hap.context.onTrigger) {
|
||||||
|
console.warn('no output chosen. use one of .out() .webdirt() .osc()');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let started;
|
||||||
|
document.getElementById('start').addEventListener('click', async () => {
|
||||||
|
const code = document.getElementById('text').value;
|
||||||
|
const pattern = eval(code);
|
||||||
|
const events = pattern._firstCycleValues;
|
||||||
|
console.log(code, '->', events);
|
||||||
|
scheduler.setPattern(pattern);
|
||||||
|
if (!started) {
|
||||||
|
scheduler.start();
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!dist
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# vite-vanilla-repl
|
||||||
|
|
||||||
|
This folder demonstrates how to set up a strudel repl using vite and vanilla JS. Run it using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
or view it [live on githack](https://rawcdn.githack.com/tidalcycles/strudel/5fb36acb046ead7cd6ad3cd10f532e7f585f536a/packages/core/examples/vite-vanilla-repl/dist/index.html)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{b as s,h as i,m,a as n,c as p,p as t}from"./index.4cbc0a10.js";export{s as SyntaxError,i as h,m as mini,n as minify,p as parse,t as patternifyAST};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
import{g as s,f as d,i as t,n as r,l as u,j as o,d as f,k as p,r as g,s as i,w as l,e as m}from"./index.4cbc0a10.js";export{s as getAudioContext,d as getCachedBuffer,t as getLoadedBuffer,r as getLoadedSamples,u as loadBuffer,o as loadGithubSamples,f as panic,p as resetLoadedSamples,g as reverseBuffer,i as samples,l as webaudioOutput,m as webaudioOutputTrigger};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite Vanilla Strudel REPL</title>
|
||||||
|
<script type="module" crossorigin src="/tidalcycles/strudel/use-acorn/packages/core/examples/vite-vanilla-repl/dist/assets/index.4cbc0a10.js"></script>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; background: #222">
|
||||||
|
<div style="display: grid; height: 100vh">
|
||||||
|
<textarea
|
||||||
|
id="text"
|
||||||
|
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
|
||||||
|
spellcheck="false"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="start"
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 10px;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 2px solid white;
|
||||||
|
background: transparent;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
evaluate
|
||||||
|
</button>
|
||||||
|
<div id="output"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite Vanilla Strudel REPL</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; background: #222">
|
||||||
|
<div style="display: grid; height: 100vh">
|
||||||
|
<textarea
|
||||||
|
id="text"
|
||||||
|
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
|
||||||
|
spellcheck="false"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="start"
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 10px;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 2px solid white;
|
||||||
|
background: transparent;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
evaluate
|
||||||
|
</button>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script type="module" src="./main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { controls, repl, evalScope, setStringParser } from '@strudel.cycles/core';
|
||||||
|
import { mini } from '@strudel.cycles/mini';
|
||||||
|
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
|
// import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
|
import tune from './tune.mjs';
|
||||||
|
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
const input = document.getElementById('text');
|
||||||
|
input.innerHTML = tune;
|
||||||
|
|
||||||
|
evalScope(
|
||||||
|
controls,
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
import('@strudel.cycles/tonal'),
|
||||||
|
);
|
||||||
|
|
||||||
|
setStringParser(mini)
|
||||||
|
|
||||||
|
const { evaluate } = repl({
|
||||||
|
defaultOutput: webaudioOutput,
|
||||||
|
getTime: () => ctx.currentTime,
|
||||||
|
// transpiler,
|
||||||
|
});
|
||||||
|
document.getElementById('start').addEventListener('click', () => {
|
||||||
|
ctx.resume();
|
||||||
|
evaluate(input.value);
|
||||||
|
});
|
||||||
@@ -0,0 +1,885 @@
|
|||||||
|
{
|
||||||
|
"name": "vite-vanilla-repl",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "vite-vanilla-repl",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/android-arm": "0.15.13",
|
||||||
|
"@esbuild/linux-loong64": "0.15.13",
|
||||||
|
"esbuild-android-64": "0.15.13",
|
||||||
|
"esbuild-android-arm64": "0.15.13",
|
||||||
|
"esbuild-darwin-64": "0.15.13",
|
||||||
|
"esbuild-darwin-arm64": "0.15.13",
|
||||||
|
"esbuild-freebsd-64": "0.15.13",
|
||||||
|
"esbuild-freebsd-arm64": "0.15.13",
|
||||||
|
"esbuild-linux-32": "0.15.13",
|
||||||
|
"esbuild-linux-64": "0.15.13",
|
||||||
|
"esbuild-linux-arm": "0.15.13",
|
||||||
|
"esbuild-linux-arm64": "0.15.13",
|
||||||
|
"esbuild-linux-mips64le": "0.15.13",
|
||||||
|
"esbuild-linux-ppc64le": "0.15.13",
|
||||||
|
"esbuild-linux-riscv64": "0.15.13",
|
||||||
|
"esbuild-linux-s390x": "0.15.13",
|
||||||
|
"esbuild-netbsd-64": "0.15.13",
|
||||||
|
"esbuild-openbsd-64": "0.15.13",
|
||||||
|
"esbuild-sunos-64": "0.15.13",
|
||||||
|
"esbuild-windows-32": "0.15.13",
|
||||||
|
"esbuild-windows-64": "0.15.13",
|
||||||
|
"esbuild-windows-arm64": "0.15.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-android-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-android-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-darwin-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-darwin-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-freebsd-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-freebsd-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-32": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-arm": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-mips64le": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-ppc64le": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-riscv64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-s390x": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-netbsd-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-openbsd-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-sunos-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-windows-32": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-windows-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-windows-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/has": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-core-module": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"has": "^1.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nanoid": {
|
||||||
|
"version": "3.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||||
|
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/picocolors": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/postcss": {
|
||||||
|
"version": "8.4.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||||
|
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.4",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"source-map-js": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/resolve": {
|
||||||
|
"version": "1.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
|
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-core-module": "^2.9.0",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rollup": {
|
||||||
|
"version": "2.79.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||||
|
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"rollup": "dist/bin/rollup"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "^0.15.9",
|
||||||
|
"postcss": "^8.4.18",
|
||||||
|
"resolve": "^1.22.1",
|
||||||
|
"rollup": "^2.79.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vite": "bin/vite.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"less": "*",
|
||||||
|
"sass": "*",
|
||||||
|
"stylus": "*",
|
||||||
|
"sugarss": "*",
|
||||||
|
"terser": "^5.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"less": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sass": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"stylus": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sugarss": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"terser": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@esbuild/android-arm": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@esbuild/linux-loong64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@esbuild/android-arm": "0.15.13",
|
||||||
|
"@esbuild/linux-loong64": "0.15.13",
|
||||||
|
"esbuild-android-64": "0.15.13",
|
||||||
|
"esbuild-android-arm64": "0.15.13",
|
||||||
|
"esbuild-darwin-64": "0.15.13",
|
||||||
|
"esbuild-darwin-arm64": "0.15.13",
|
||||||
|
"esbuild-freebsd-64": "0.15.13",
|
||||||
|
"esbuild-freebsd-arm64": "0.15.13",
|
||||||
|
"esbuild-linux-32": "0.15.13",
|
||||||
|
"esbuild-linux-64": "0.15.13",
|
||||||
|
"esbuild-linux-arm": "0.15.13",
|
||||||
|
"esbuild-linux-arm64": "0.15.13",
|
||||||
|
"esbuild-linux-mips64le": "0.15.13",
|
||||||
|
"esbuild-linux-ppc64le": "0.15.13",
|
||||||
|
"esbuild-linux-riscv64": "0.15.13",
|
||||||
|
"esbuild-linux-s390x": "0.15.13",
|
||||||
|
"esbuild-netbsd-64": "0.15.13",
|
||||||
|
"esbuild-openbsd-64": "0.15.13",
|
||||||
|
"esbuild-sunos-64": "0.15.13",
|
||||||
|
"esbuild-windows-32": "0.15.13",
|
||||||
|
"esbuild-windows-64": "0.15.13",
|
||||||
|
"esbuild-windows-arm64": "0.15.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"esbuild-android-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-android-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-darwin-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-darwin-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-freebsd-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-freebsd-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-32": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-arm": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-mips64le": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-ppc64le": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-riscv64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-s390x": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-netbsd-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-openbsd-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-sunos-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-windows-32": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-windows-64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-windows-arm64": {
|
||||||
|
"version": "0.15.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
|
||||||
|
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"function-bind": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"has": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"function-bind": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-core-module": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nanoid": {
|
||||||
|
"version": "3.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||||
|
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"picocolors": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"postcss": {
|
||||||
|
"version": "8.4.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||||
|
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"nanoid": "^3.3.4",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"source-map-js": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"version": "1.22.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
|
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-core-module": "^2.9.0",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rollup": {
|
||||||
|
"version": "2.79.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||||
|
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source-map-js": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esbuild": "^0.15.9",
|
||||||
|
"fsevents": "~2.3.2",
|
||||||
|
"postcss": "^8.4.18",
|
||||||
|
"resolve": "^1.22.1",
|
||||||
|
"rollup": "^2.79.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "vite-vanilla-repl",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build-githack": "vite build --base /tidalcycles/strudel/use-acorn/packages/core/examples/vite-vanilla-repl/dist/",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^3.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/* export default `await samples('github:tidalcycles/Dirt-Samples/master')
|
||||||
|
|
||||||
|
stack(
|
||||||
|
// amen
|
||||||
|
n("0 1 2 3 4 5 6 7")
|
||||||
|
.sometimes(x=>x.ply(2))
|
||||||
|
.rarely(x=>x.speed("2 | -2"))
|
||||||
|
.sometimesBy(.4, x=>x.delay(".5"))
|
||||||
|
.s("amencutup")
|
||||||
|
.slow(2)
|
||||||
|
.room(.5)
|
||||||
|
,
|
||||||
|
// bass
|
||||||
|
sine.add(saw.slow(4)).range(0,7).segment(8)
|
||||||
|
.superimpose(x=>x.add(.1))
|
||||||
|
.scale('G0 minor').note()
|
||||||
|
.s("sawtooth").decay(.1).sustain(0)
|
||||||
|
.gain(.4).cutoff(perlin.range(300,3000).slow(8)).resonance(10)
|
||||||
|
.degradeBy("0 0.1 .5 .1")
|
||||||
|
.rarely(add(note("12")))
|
||||||
|
,
|
||||||
|
// chord
|
||||||
|
note("Bb3,D4".superimpose(x=>x.add(.2)))
|
||||||
|
.s('sawtooth').cutoff(1000).struct("<~@3 [~ x]>")
|
||||||
|
.decay(.05).sustain(.0).delay(.8).delaytime(.125).room(.8)
|
||||||
|
,
|
||||||
|
// alien
|
||||||
|
s("breath").room(1).shape(.6).chop(16).rev().mask("<x ~@7>")
|
||||||
|
,
|
||||||
|
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
|
||||||
|
).reset("<x@7 x(5,8)>")`;
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default `stack(
|
||||||
|
n("c3 [eb3,g3]")
|
||||||
|
.delay("<0 .5>")
|
||||||
|
.delaytime(".16 | .33")
|
||||||
|
.delayfeedback(".6 | .8")
|
||||||
|
).sometimes(x=>x.speed("-1"))`;
|
||||||
@@ -4,10 +4,10 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
|||||||
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/>.
|
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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './controls.mjs';
|
import controls from './controls.mjs';
|
||||||
export * from './euclid.mjs';
|
export * from './euclid.mjs';
|
||||||
import Fraction from './fraction.mjs';
|
import Fraction from './fraction.mjs';
|
||||||
export { Fraction };
|
export { Fraction, controls };
|
||||||
export * from './hap.mjs';
|
export * from './hap.mjs';
|
||||||
export * from './pattern.mjs';
|
export * from './pattern.mjs';
|
||||||
export * from './signal.mjs';
|
export * from './signal.mjs';
|
||||||
@@ -15,6 +15,9 @@ export * from './state.mjs';
|
|||||||
export * from './timespan.mjs';
|
export * from './timespan.mjs';
|
||||||
export * from './util.mjs';
|
export * from './util.mjs';
|
||||||
export * from './speak.mjs';
|
export * from './speak.mjs';
|
||||||
|
export * from './evaluate.mjs';
|
||||||
|
export * from './repl.mjs';
|
||||||
|
export { default as drawLine } from './drawLine.mjs';
|
||||||
export { default as gist } from './gist.js';
|
export { default as gist } from './gist.js';
|
||||||
// below won't work with runtime.mjs (json import fails)
|
// below won't work with runtime.mjs (json import fails)
|
||||||
/* import * as p from './package.json';
|
/* import * as p from './package.json';
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/core",
|
"name": "@strudel.cycles/core",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/core",
|
"name": "@strudel.cycles/core",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "Port of Tidal Cycles to JavaScript",
|
"description": "Port of Tidal Cycles to JavaScript",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+48
-50
@@ -10,9 +10,15 @@ import Hap from './hap.mjs';
|
|||||||
import State from './state.mjs';
|
import State from './state.mjs';
|
||||||
import { unionWithObj } from './value.mjs';
|
import { unionWithObj } from './value.mjs';
|
||||||
|
|
||||||
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs';
|
import { compose, removeUndefineds, flatten, id, listRange, curry, mod, numeralArgs, parseNumeral } from './util.mjs';
|
||||||
import drawLine from './drawLine.mjs';
|
import drawLine from './drawLine.mjs';
|
||||||
|
|
||||||
|
let stringParser;
|
||||||
|
// parser is expected to turn a string into a pattern
|
||||||
|
// if set, the reify function will parse all strings with it
|
||||||
|
// intended to use with mini to automatically interpret all strings as mini notation
|
||||||
|
export const setStringParser = (parser) => (stringParser = parser);
|
||||||
|
|
||||||
/** @class Class representing a pattern. */
|
/** @class Class representing a pattern. */
|
||||||
export class Pattern {
|
export class Pattern {
|
||||||
_Pattern = true; // this property is used to detect if a pattern that fails instanceof Pattern is an instance of another Pattern
|
_Pattern = true; // this property is used to detect if a pattern that fails instanceof Pattern is an instance of another Pattern
|
||||||
@@ -32,8 +38,10 @@ export class Pattern {
|
|||||||
* @param {Fraction | number} end to time
|
* @param {Fraction | number} end to time
|
||||||
* @returns Hap[]
|
* @returns Hap[]
|
||||||
* @example
|
* @example
|
||||||
* const pattern = sequence('a', ['b', 'c']);
|
* const pattern = sequence('a', ['b', 'c'])
|
||||||
* const haps = pattern.queryArc(0, 1);
|
* const haps = pattern.queryArc(0, 1)
|
||||||
|
* console.log(haps)
|
||||||
|
* silence
|
||||||
*/
|
*/
|
||||||
queryArc(begin, end) {
|
queryArc(begin, end) {
|
||||||
return this.query(new State(new TimeSpan(begin, end)));
|
return this.query(new State(new TimeSpan(begin, end)));
|
||||||
@@ -63,6 +71,17 @@ export class Pattern {
|
|||||||
return new Pattern((state) => this.query(state.withSpan(func)));
|
return new Pattern((state) => this.query(state.withSpan(func)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withQuerySpanMaybe(func) {
|
||||||
|
const pat = this;
|
||||||
|
return new Pattern((state) => {
|
||||||
|
const newState = state.withSpan(func);
|
||||||
|
if (!newState.span) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return pat.query(newState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As with {@link Pattern#withQuerySpan}, but the function is applied to both the
|
* As with {@link Pattern#withQuerySpan}, but the function is applied to both the
|
||||||
* begin and end time of the query timespan.
|
* begin and end time of the query timespan.
|
||||||
@@ -435,34 +454,8 @@ export class Pattern {
|
|||||||
return otherPat.fmap((b) => this.fmap((a) => func(a)(b)))._TrigzeroJoin();
|
return otherPat.fmap((b) => this.fmap((a) => func(a)(b)))._TrigzeroJoin();
|
||||||
}
|
}
|
||||||
|
|
||||||
_asNumber(dropfails = false, softfail = false) {
|
_asNumber() {
|
||||||
return this._withHap((hap) => {
|
return this.fmap(parseNumeral);
|
||||||
const asNumber = Number(hap.value);
|
|
||||||
if (!isNaN(asNumber)) {
|
|
||||||
return hap.withValue(() => asNumber);
|
|
||||||
}
|
|
||||||
const specialValue = {
|
|
||||||
e: Math.E,
|
|
||||||
pi: Math.PI,
|
|
||||||
}[hap.value];
|
|
||||||
if (typeof specialValue !== 'undefined') {
|
|
||||||
return hap.withValue(() => specialValue);
|
|
||||||
}
|
|
||||||
if (isNote(hap.value)) {
|
|
||||||
// set context type to midi to let the player know its meant as midi number and not as frequency
|
|
||||||
return new Hap(hap.whole, hap.part, toMidi(hap.value), { ...hap.context, type: 'midi' });
|
|
||||||
}
|
|
||||||
if (dropfails) {
|
|
||||||
// return 'nothing'
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (softfail) {
|
|
||||||
// return original hap
|
|
||||||
return hap;
|
|
||||||
}
|
|
||||||
throw new Error('cannot parse as number: "' + hap.value + '"');
|
|
||||||
return hap;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -744,12 +737,17 @@ export class Pattern {
|
|||||||
// // there is no gap.. so maybe revert to _fast?
|
// // there is no gap.. so maybe revert to _fast?
|
||||||
// return this._fast(factor)
|
// return this._fast(factor)
|
||||||
// }
|
// }
|
||||||
|
// A bit fiddly, to drop zero-width queries at the start of the next cycle
|
||||||
const qf = function (span) {
|
const qf = function (span) {
|
||||||
const cycle = span.begin.sam();
|
const cycle = span.begin.sam();
|
||||||
const begin = cycle.add(span.begin.sub(cycle).mul(factor).min(1));
|
const bpos = span.begin.sub(cycle).mul(factor).min(1);
|
||||||
const end = cycle.add(span.end.sub(cycle).mul(factor).min(1));
|
const epos = span.end.sub(cycle).mul(factor).min(1);
|
||||||
return new TimeSpan(begin, end);
|
if (bpos >= 1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return new TimeSpan(cycle.add(bpos), cycle.add(epos));
|
||||||
};
|
};
|
||||||
|
// Also fiddly, to maintain the right 'whole' relative to the part
|
||||||
const ef = function (hap) {
|
const ef = function (hap) {
|
||||||
const begin = hap.part.begin;
|
const begin = hap.part.begin;
|
||||||
const end = hap.part.end;
|
const end = hap.part.end;
|
||||||
@@ -765,7 +763,7 @@ export class Pattern {
|
|||||||
);
|
);
|
||||||
return new Hap(newWhole, newPart, hap.value, hap.context);
|
return new Hap(newWhole, newPart, hap.value, hap.context);
|
||||||
};
|
};
|
||||||
return this.withQuerySpan(qf)._withHap(ef)._splitQueries();
|
return this.withQuerySpanMaybe(qf)._withHap(ef)._splitQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compress each cycle into the given timespan, leaving a gap
|
// Compress each cycle into the given timespan, leaving a gap
|
||||||
@@ -1370,9 +1368,6 @@ function _composeOp(a, b, func) {
|
|||||||
|
|
||||||
// Make composers
|
// Make composers
|
||||||
(function () {
|
(function () {
|
||||||
const num = (pat) => pat._asNumber();
|
|
||||||
const numOrString = (pat) => pat._asNumber(false, true);
|
|
||||||
|
|
||||||
// pattern composers
|
// pattern composers
|
||||||
const composers = {
|
const composers = {
|
||||||
set: [(a, b) => b],
|
set: [(a, b) => b],
|
||||||
@@ -1396,7 +1391,7 @@ function _composeOp(a, b, func) {
|
|||||||
* // Behind the scenes, the notes are converted to midi numbers:
|
* // Behind the scenes, the notes are converted to midi numbers:
|
||||||
* // "48 52 55".add("<0 5 7 0>").note()
|
* // "48 52 55".add("<0 5 7 0>").note()
|
||||||
*/
|
*/
|
||||||
add: [(a, b) => a + b, numOrString], // support string concatenation
|
add: [numeralArgs((a, b) => a + b)], // support string concatenation
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Like add, but the given numbers are subtracted.
|
* Like add, but the given numbers are subtracted.
|
||||||
@@ -1406,7 +1401,7 @@ function _composeOp(a, b, func) {
|
|||||||
* "0 2 4".sub("<0 1 2 3>").scale('C4 minor').note()
|
* "0 2 4".sub("<0 1 2 3>").scale('C4 minor').note()
|
||||||
* // See add for more information.
|
* // See add for more information.
|
||||||
*/
|
*/
|
||||||
sub: [(a, b) => a - b, num],
|
sub: [numeralArgs((a, b) => a - b)],
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Multiplies each number by the given factor.
|
* Multiplies each number by the given factor.
|
||||||
@@ -1415,21 +1410,21 @@ function _composeOp(a, b, func) {
|
|||||||
* @example
|
* @example
|
||||||
* "1 1.5 [1.66, <2 2.33>]".mul(150).freq()
|
* "1 1.5 [1.66, <2 2.33>]".mul(150).freq()
|
||||||
*/
|
*/
|
||||||
mul: [(a, b) => a * b, num],
|
mul: [numeralArgs((a, b) => a * b)],
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Divides each number by the given factor.
|
* Divides each number by the given factor.
|
||||||
* @name div
|
* @name div
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
*/
|
*/
|
||||||
div: [(a, b) => a / b, num],
|
div: [numeralArgs((a, b) => a / b)],
|
||||||
mod: [mod, num],
|
mod: [numeralArgs(mod)],
|
||||||
pow: [Math.pow, num],
|
pow: [numeralArgs(Math.pow)],
|
||||||
_and: [(a, b) => a & b, num],
|
_and: [numeralArgs((a, b) => a & b)],
|
||||||
_or: [(a, b) => a | b, num],
|
_or: [numeralArgs((a, b) => a | b)],
|
||||||
_xor: [(a, b) => a ^ b, num],
|
_xor: [numeralArgs((a, b) => a ^ b)],
|
||||||
_lshift: [(a, b) => a << b, num],
|
_lshift: [numeralArgs((a, b) => a << b)],
|
||||||
_rshift: [(a, b) => a >> b, num],
|
_rshift: [numeralArgs((a, b) => a >> b)],
|
||||||
|
|
||||||
// TODO - force numerical comparison if both look like numbers?
|
// TODO - force numerical comparison if both look like numbers?
|
||||||
lt: [(a, b) => a < b],
|
lt: [(a, b) => a < b],
|
||||||
@@ -1556,7 +1551,7 @@ export function pure(value) {
|
|||||||
|
|
||||||
export function isPattern(thing) {
|
export function isPattern(thing) {
|
||||||
// thing?.constructor?.name !== 'Pattern' // <- this will fail when code is mangled
|
// thing?.constructor?.name !== 'Pattern' // <- this will fail when code is mangled
|
||||||
const is = thing instanceof Pattern || thing._Pattern;
|
const is = thing instanceof Pattern || thing?._Pattern;
|
||||||
if (!thing instanceof Pattern) {
|
if (!thing instanceof Pattern) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Found Pattern that fails "instanceof Pattern" check.
|
`Found Pattern that fails "instanceof Pattern" check.
|
||||||
@@ -1572,6 +1567,9 @@ export function reify(thing) {
|
|||||||
if (isPattern(thing)) {
|
if (isPattern(thing)) {
|
||||||
return thing;
|
return thing;
|
||||||
}
|
}
|
||||||
|
if (stringParser && typeof thing === 'string') {
|
||||||
|
return stringParser(thing);
|
||||||
|
}
|
||||||
return pure(thing);
|
return pure(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Cyclist } from './cyclist.mjs';
|
||||||
|
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||||
|
|
||||||
|
export function repl({ interval, defaultOutput, onSchedulerError, onEvalError, onEval, getTime, transpiler }) {
|
||||||
|
const scheduler = new Cyclist({ interval, onTrigger: defaultOutput, onError: onSchedulerError, getTime });
|
||||||
|
const evaluate = async (code) => {
|
||||||
|
if (!code) {
|
||||||
|
throw new Error('no code to evaluate');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { pattern } = await _evaluate(code, transpiler);
|
||||||
|
scheduler.setPattern(pattern, true);
|
||||||
|
onEval?.({
|
||||||
|
pattern,
|
||||||
|
code,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`eval error: ${err.message}`);
|
||||||
|
onEvalError?.(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return { scheduler, evaluate };
|
||||||
|
}
|
||||||
@@ -238,6 +238,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
|
|||||||
|
|
||||||
export const wchooseCycles = (...pairs) => _wchooseWith(rand, ...pairs).innerJoin();
|
export const wchooseCycles = (...pairs) => _wchooseWith(rand, ...pairs).innerJoin();
|
||||||
|
|
||||||
|
// this function expects pat to be a pattern of floats...
|
||||||
export const perlinWith = (pat) => {
|
export const perlinWith = (pat) => {
|
||||||
const pata = pat.fmap(Math.floor);
|
const pata = pat.fmap(Math.floor);
|
||||||
const patb = pat.fmap((t) => Math.floor(t) + 1);
|
const patb = pat.fmap((t) => Math.floor(t) + 1);
|
||||||
@@ -255,7 +256,7 @@ export const perlinWith = (pat) => {
|
|||||||
* s("bd sd,hh*4").cutoff(perlin.range(500,2000))
|
* s("bd sd,hh*4").cutoff(perlin.range(500,2000))
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const perlin = perlinWith(time);
|
export const perlin = perlinWith(time.fmap((v) => Number(v)));
|
||||||
|
|
||||||
Pattern.prototype._degradeByWith = function (withPat, x) {
|
Pattern.prototype._degradeByWith = function (withPat, x) {
|
||||||
return this.fmap((a) => (_) => a).appLeft(withPat._filterValues((v) => v > x));
|
return this.fmap((a) => (_) => a).appLeft(withPat._filterValues((v) => v > x));
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ import {
|
|||||||
|
|
||||||
import { steady } from '../signal.mjs';
|
import { steady } from '../signal.mjs';
|
||||||
|
|
||||||
|
import controls from '../controls.mjs';
|
||||||
|
|
||||||
|
const { n } = controls;
|
||||||
const st = (begin, end) => new State(ts(begin, end));
|
const st = (begin, end) => new State(ts(begin, end));
|
||||||
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
|
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
|
||||||
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
|
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
|
||||||
@@ -137,6 +140,9 @@ describe('Pattern', () => {
|
|||||||
it('Can make a pattern', () => {
|
it('Can make a pattern', () => {
|
||||||
expect(pure('hello').query(st(0.5, 2.5)).length).toBe(3);
|
expect(pure('hello').query(st(0.5, 2.5)).length).toBe(3);
|
||||||
});
|
});
|
||||||
|
it('Supports zero-width queries', () => {
|
||||||
|
expect(pure('hello').queryArc(0, 0).length).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('fmap()', () => {
|
describe('fmap()', () => {
|
||||||
it('Can add things', () => {
|
it('Can add things', () => {
|
||||||
@@ -191,6 +197,9 @@ describe('Pattern', () => {
|
|||||||
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
|
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('can add object patterns', () => {
|
||||||
|
sameFirst(n(sequence(1, [2, 3])).add(n(10)), n(sequence(11, [12, 13])));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('keep()', () => {
|
describe('keep()', () => {
|
||||||
it('can structure In()', () => {
|
it('can structure In()', () => {
|
||||||
@@ -373,9 +382,10 @@ describe('Pattern', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('copes with breaking up events across cycles', () => {
|
it('copes with breaking up events across cycles', () => {
|
||||||
expect(pure('a').slow(2)._fastGap(2)._setContext({}).query(st(0, 2))).toStrictEqual(
|
expect(pure('a').slow(2)._fastGap(2)._setContext({}).query(st(0, 2))).toStrictEqual([
|
||||||
[hap(ts(0, 1), ts(0, 0.5), 'a'), hap(ts(0.5, 1.5), ts(1, 1.5), 'a')]
|
hap(ts(0, 1), ts(0, 0.5), 'a'),
|
||||||
);
|
hap(ts(0.5, 1.5), ts(1, 1.5), 'a'),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('_compressSpan()', () => {
|
describe('_compressSpan()', () => {
|
||||||
@@ -430,6 +440,16 @@ describe('Pattern', () => {
|
|||||||
// mini('[c3 g3]/2 eb3') always plays [c3 eb3]
|
// mini('[c3 g3]/2 eb3') always plays [c3 eb3]
|
||||||
// mini('eb3 [c3 g3]/2 ') always plays [c3 g3]
|
// mini('eb3 [c3 g3]/2 ') always plays [c3 g3]
|
||||||
});
|
});
|
||||||
|
it('Supports zero-length queries', () => {
|
||||||
|
expect(steady('a')._slow(1).queryArc(0, 0)).toStrictEqual(steady('a').queryArc(0, 0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('slow()', () => {
|
||||||
|
it('Supports zero-length queries', () => {
|
||||||
|
expect(steady('a').slow(1)._setContext({}).queryArc(0, 0)).toStrictEqual(
|
||||||
|
steady('a')._setContext({}).queryArc(0, 0),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('inside', () => {
|
describe('inside', () => {
|
||||||
it('can rev inside a cycle', () => {
|
it('can rev inside a cycle', () => {
|
||||||
@@ -799,10 +819,11 @@ describe('Pattern', () => {
|
|||||||
});
|
});
|
||||||
it('Squeezes to the correct cycle', () => {
|
it('Squeezes to the correct cycle', () => {
|
||||||
expect(
|
expect(
|
||||||
pure(time.struct(true))._squeezeJoin().queryArc(3,4).map(x => x.value)
|
pure(time.struct(true))
|
||||||
).toStrictEqual(
|
._squeezeJoin()
|
||||||
[Fraction(3.5)]
|
.queryArc(3, 4)
|
||||||
)
|
.map((x) => x.value),
|
||||||
|
).toStrictEqual([Fraction(3.5)]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('ply', () => {
|
describe('ply', () => {
|
||||||
@@ -855,9 +876,7 @@ describe('Pattern', () => {
|
|||||||
});
|
});
|
||||||
describe('range', () => {
|
describe('range', () => {
|
||||||
it('Can be patterned', () => {
|
it('Can be patterned', () => {
|
||||||
expect(sequence(0, 0).range(sequence(0, 0.5), 1).firstCycle()).toStrictEqual(
|
expect(sequence(0, 0).range(sequence(0, 0.5), 1).firstCycle()).toStrictEqual(sequence(0, 0.5).firstCycle());
|
||||||
sequence(0, 0.5).firstCycle(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('range2', () => {
|
describe('range2', () => {
|
||||||
|
|||||||
@@ -5,7 +5,20 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { pure } from '../pattern.mjs';
|
import { pure } from '../pattern.mjs';
|
||||||
import { isNote, tokenizeNote, toMidi, fromMidi, mod, compose, getFrequency, getPlayableNoteValue } from '../util.mjs';
|
import {
|
||||||
|
isNote,
|
||||||
|
tokenizeNote,
|
||||||
|
toMidi,
|
||||||
|
fromMidi,
|
||||||
|
mod,
|
||||||
|
compose,
|
||||||
|
getFrequency,
|
||||||
|
getPlayableNoteValue,
|
||||||
|
parseNumeral,
|
||||||
|
parseFractional,
|
||||||
|
numeralArgs,
|
||||||
|
fractionalArgs,
|
||||||
|
} from '../util.mjs';
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
describe('isNote', () => {
|
describe('isNote', () => {
|
||||||
@@ -92,16 +105,16 @@ describe('getFrequency', () => {
|
|||||||
expect(getFrequency(happify(57, { type: 'midi' }))).toEqual(220);
|
expect(getFrequency(happify(57, { type: 'midi' }))).toEqual(220);
|
||||||
});
|
});
|
||||||
it('should return frequencies unchanged', () => {
|
it('should return frequencies unchanged', () => {
|
||||||
expect(getFrequency(happify(440, { type: 'frequency' }))).toEqual(440);
|
expect(getFrequency(happify(440, { type: 'frequency' }))).toEqual(440);
|
||||||
expect(getFrequency(happify(432, { type: 'frequency' }))).toEqual(432);
|
expect(getFrequency(happify(432, { type: 'frequency' }))).toEqual(432);
|
||||||
});
|
});
|
||||||
it('should turn object with a "freq" property into frequency', () => {
|
it('should turn object with a "freq" property into frequency', () => {
|
||||||
expect(getFrequency(happify({freq: 220}))).toEqual(220)
|
expect(getFrequency(happify({ freq: 220 }))).toEqual(220);
|
||||||
expect(getFrequency(happify({freq: 440}))).toEqual(440)
|
expect(getFrequency(happify({ freq: 440 }))).toEqual(440);
|
||||||
});
|
});
|
||||||
it('should throw an error when given a non-note', () => {
|
it('should throw an error when given a non-note', () => {
|
||||||
expect(() => getFrequency(happify('Q'))).toThrowError(`not a note or frequency: Q`)
|
expect(() => getFrequency(happify('Q'))).toThrowError(`not a note or frequency: Q`);
|
||||||
expect(() => getFrequency(happify('Z'))).toThrowError(`not a note or frequency: Z`)
|
expect(() => getFrequency(happify('Z'))).toThrowError(`not a note or frequency: Z`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,22 +153,72 @@ describe('compose', () => {
|
|||||||
describe('getPlayableNoteValue', () => {
|
describe('getPlayableNoteValue', () => {
|
||||||
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
|
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
|
||||||
it('should return object "note" property', () => {
|
it('should return object "note" property', () => {
|
||||||
expect(getPlayableNoteValue(happify({note: "a4"}))).toEqual('a4')
|
expect(getPlayableNoteValue(happify({ note: 'a4' }))).toEqual('a4');
|
||||||
});
|
});
|
||||||
it('should return object "n" property', () => {
|
it('should return object "n" property', () => {
|
||||||
expect(getPlayableNoteValue(happify({n: "a4"}))).toEqual('a4')
|
expect(getPlayableNoteValue(happify({ n: 'a4' }))).toEqual('a4');
|
||||||
});
|
});
|
||||||
it('should return object "value" property', () => {
|
it('should return object "value" property', () => {
|
||||||
expect(getPlayableNoteValue(happify({value: "a4"}))).toEqual('a4')
|
expect(getPlayableNoteValue(happify({ value: 'a4' }))).toEqual('a4');
|
||||||
});
|
});
|
||||||
it('should turn midi into frequency', () => {
|
it('should turn midi into frequency', () => {
|
||||||
expect(getPlayableNoteValue(happify(57, {type: 'midi'}))).toEqual(220)
|
expect(getPlayableNoteValue(happify(57, { type: 'midi' }))).toEqual(220);
|
||||||
})
|
});
|
||||||
it('should return frequency value', () => {
|
it('should return frequency value', () => {
|
||||||
expect(getPlayableNoteValue(happify(220, {type: 'frequency'}))).toEqual(220)
|
expect(getPlayableNoteValue(happify(220, { type: 'frequency' }))).toEqual(220);
|
||||||
})
|
});
|
||||||
it('should throw an error if value is not an object, number, or string', () => {
|
it('should throw an error if value is not an object, number, or string', () => {
|
||||||
expect(() => getPlayableNoteValue(happify(false))).toThrowError(`not a note: false`)
|
expect(() => getPlayableNoteValue(happify(false))).toThrowError(`not a note: false`);
|
||||||
expect(() => getPlayableNoteValue(happify(undefined))).toThrowError(`not a note: undefined`)
|
expect(() => getPlayableNoteValue(happify(undefined))).toThrowError(`not a note: undefined`);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parseNumeral', () => {
|
||||||
|
it('should parse numbers as is', () => {
|
||||||
|
expect(parseNumeral(4)).toBe(4);
|
||||||
|
expect(parseNumeral(0)).toBe(0);
|
||||||
|
expect(parseNumeral(20)).toBe(20);
|
||||||
|
expect(parseNumeral('20')).toBe(20);
|
||||||
|
expect(parseNumeral(1.5)).toBe(1.5);
|
||||||
|
});
|
||||||
|
it('should parse notes', () => {
|
||||||
|
expect(parseNumeral('c4')).toBe(60);
|
||||||
|
expect(parseNumeral('c#4')).toBe(61);
|
||||||
|
expect(parseNumeral('db4')).toBe(61);
|
||||||
|
});
|
||||||
|
it('should throw an error for unknown strings', () => {
|
||||||
|
expect(() => parseNumeral('xyz')).toThrowError('cannot parse as numeral: "xyz"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseFractional', () => {
|
||||||
|
it('should parse numbers as is', () => {
|
||||||
|
expect(parseFractional(4)).toBe(4);
|
||||||
|
expect(parseFractional(0)).toBe(0);
|
||||||
|
expect(parseFractional(20)).toBe(20);
|
||||||
|
expect(parseFractional('20')).toBe(20);
|
||||||
|
expect(parseFractional(1.5)).toBe(1.5);
|
||||||
|
});
|
||||||
|
it('should parse fractional shorthands values', () => {
|
||||||
|
expect(parseFractional('w')).toBe(1);
|
||||||
|
expect(parseFractional('h')).toBe(0.5);
|
||||||
|
expect(parseFractional('q')).toBe(0.25);
|
||||||
|
expect(parseFractional('e')).toBe(0.125);
|
||||||
|
});
|
||||||
|
it('should throw an error for unknown strings', () => {
|
||||||
|
expect(() => parseFractional('xyz')).toThrowError('cannot parse as fractional: "xyz"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('numeralArgs', () => {
|
||||||
|
it('should convert function arguments to numbers', () => {
|
||||||
|
const add = numeralArgs((a, b) => a + b);
|
||||||
|
expect(add('c4', 2)).toBe(62);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('fractionalArgs', () => {
|
||||||
|
it('should convert function arguments to numbers', () => {
|
||||||
|
const add = fractionalArgs((a, b) => a + b);
|
||||||
|
expect(add('q', 2)).toBe(2.25);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export class TimeSpan {
|
|||||||
const end = this.end;
|
const end = this.end;
|
||||||
const end_sam = end.sam();
|
const end_sam = end.sam();
|
||||||
|
|
||||||
|
// Support zero-width timespans
|
||||||
|
if (begin.equals(end)) {
|
||||||
|
return([new TimeSpan(begin, end)]);
|
||||||
|
}
|
||||||
|
|
||||||
while (end.gt(begin)) {
|
while (end.gt(begin)) {
|
||||||
// If begin and end are in the same cycle, we're done.
|
// If begin and end are in the same cycle, we're done.
|
||||||
if (begin.sam().equals(end_sam)) {
|
if (begin.sam().equals(end_sam)) {
|
||||||
|
|||||||
+48
-2
@@ -73,8 +73,11 @@ export const getPlayableNoteValue = (hap) => {
|
|||||||
export const getFrequency = (hap) => {
|
export const getFrequency = (hap) => {
|
||||||
let { value, context } = hap;
|
let { value, context } = hap;
|
||||||
// if value is number => interpret as midi number as long as its not marked as frequency
|
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||||
if (typeof value === 'object' && value.freq) {
|
if (typeof value === 'object') {
|
||||||
return value.freq;
|
if (value.freq) {
|
||||||
|
return value.freq;
|
||||||
|
}
|
||||||
|
return getFreq(value.note || value.n || value.value);
|
||||||
}
|
}
|
||||||
if (typeof value === 'number' && context.type !== 'frequency') {
|
if (typeof value === 'number' && context.type !== 'frequency') {
|
||||||
value = fromMidi(hap.value);
|
value = fromMidi(hap.value);
|
||||||
@@ -130,3 +133,46 @@ export function curry(func, overload) {
|
|||||||
}
|
}
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseNumeral(numOrString) {
|
||||||
|
const asNumber = Number(numOrString);
|
||||||
|
if (!isNaN(asNumber)) {
|
||||||
|
return asNumber;
|
||||||
|
}
|
||||||
|
if (isNote(numOrString)) {
|
||||||
|
return toMidi(numOrString);
|
||||||
|
}
|
||||||
|
throw new Error(`cannot parse as numeral: "${numOrString}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapArgs(fn, mapFn) {
|
||||||
|
return (...args) => fn(...args.map(mapFn));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numeralArgs(fn) {
|
||||||
|
return mapArgs(fn, parseNumeral);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseFractional(numOrString) {
|
||||||
|
const asNumber = Number(numOrString);
|
||||||
|
if (!isNaN(asNumber)) {
|
||||||
|
return asNumber;
|
||||||
|
}
|
||||||
|
const specialValue = {
|
||||||
|
pi: Math.PI,
|
||||||
|
w: 1,
|
||||||
|
h: 0.5,
|
||||||
|
q: 0.25,
|
||||||
|
e: 0.125,
|
||||||
|
s: 0.0625,
|
||||||
|
t: 1 / 3,
|
||||||
|
f: 0.2,
|
||||||
|
x: 1 / 6,
|
||||||
|
}[numOrString];
|
||||||
|
if (typeof specialValue !== 'undefined') {
|
||||||
|
return specialValue;
|
||||||
|
}
|
||||||
|
throw new Error(`cannot parse as fractional: "${numOrString}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fractionalArgs = (fn) => mapArgs(fn, parseFractional);
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// will move to https://github.com/felixroos/zyklus
|
||||||
|
// TODO: started flag
|
||||||
|
|
||||||
|
function createClock(
|
||||||
|
getTime,
|
||||||
|
callback, // called slightly before each cycle
|
||||||
|
duration = 0.05, // duration of each cycle
|
||||||
|
interval = 0.1, // interval between callbacks
|
||||||
|
overlap = 0.1, // overlap between callbacks
|
||||||
|
) {
|
||||||
|
let tick = 0; // counts callbacks
|
||||||
|
let phase = 0; // next callback time
|
||||||
|
let precision = 10 ** 4; // used to round phase
|
||||||
|
let minLatency = 0.01;
|
||||||
|
const setDuration = (setter) => (duration = setter(duration));
|
||||||
|
overlap = overlap || interval / 2;
|
||||||
|
const onTick = () => {
|
||||||
|
const t = getTime();
|
||||||
|
const lookahead = t + interval + overlap; // the time window for this tick
|
||||||
|
if (phase === 0) {
|
||||||
|
phase = t + minLatency;
|
||||||
|
}
|
||||||
|
// callback as long as we're inside the lookahead
|
||||||
|
while (phase < lookahead) {
|
||||||
|
phase = Math.round(phase * precision) / precision;
|
||||||
|
phase >= t && callback(phase, duration, tick);
|
||||||
|
phase < t && console.log('TOO LATE', phase); // what if latency is added from outside?
|
||||||
|
phase += duration; // increment phase by duration
|
||||||
|
tick++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let intervalID;
|
||||||
|
const start = () => {
|
||||||
|
onTick();
|
||||||
|
intervalID = setInterval(onTick, interval * 1000);
|
||||||
|
};
|
||||||
|
const clear = () => clearInterval(intervalID);
|
||||||
|
const pause = () => clear();
|
||||||
|
const stop = () => {
|
||||||
|
tick = 0;
|
||||||
|
phase = 0;
|
||||||
|
clear();
|
||||||
|
};
|
||||||
|
const getPhase = () => phase;
|
||||||
|
// setCallback
|
||||||
|
return { setDuration, start, stop, pause, duration, getPhase };
|
||||||
|
}
|
||||||
|
export default createClock;
|
||||||
@@ -10,15 +10,22 @@ Either install with `npm i @strudel.cycles/embed` or just use a cdn to import th
|
|||||||
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
|
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
|
||||||
<strudel-repl>
|
<strudel-repl>
|
||||||
<!--
|
<!--
|
||||||
"a4 [a3 c3] a3 c3".color('#F9D649')
|
note(`[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
.sub("<7 12 5 12>".slow(2))
|
[a4 [a4 c5] e5 [d5 c5]]
|
||||||
.off(1/4,x=>x.add(7).color("#FFFFFF #0C3AA1 #C63928"))
|
[b4 [~ c5] d5 e5]
|
||||||
.off(1/8,x=>x.add(12).color('#215CB6'))
|
[c5 a4 a4 ~]
|
||||||
.slow(2)
|
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||||
.legato(sine.range(0.3, 2).slow(28))
|
[e5 [~ c5] e5 [d5 c5]]
|
||||||
.wave("sawtooth square".fast(2))
|
[b4 [b4 c5] d5 e5]
|
||||||
.filter('lowpass', cosine.range(500,4000).slow(16))
|
[c5 a4 a4 ~]],
|
||||||
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})
|
[[e2 e3]*4]
|
||||||
|
[[a2 a3]*4]
|
||||||
|
[[g#2 g#3]*2 [e2 e3]*2]
|
||||||
|
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||||
|
[[d2 d3]*4]
|
||||||
|
[[c2 c3]*4]
|
||||||
|
[[b1 b2]*2 [e2 e3]*2]
|
||||||
|
[[a1 a2]*4]`).slow(16)
|
||||||
-->
|
-->
|
||||||
</strudel-repl>
|
</strudel-repl>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,14 +2,21 @@
|
|||||||
<!-- <script src="./embed.js"></script> -->
|
<!-- <script src="./embed.js"></script> -->
|
||||||
<strudel-repl>
|
<strudel-repl>
|
||||||
<!--
|
<!--
|
||||||
"a4 [a3 c3] a3 c3".color('#F9D649')
|
note(`[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
.sub("<7 12 5 12>".slow(2))
|
[a4 [a4 c5] e5 [d5 c5]]
|
||||||
.off(1/4,x=>x.add(7).color("#FFFFFF #0C3AA1 #C63928"))
|
[b4 [~ c5] d5 e5]
|
||||||
.off(1/8,x=>x.add(12).color('#215CB6'))
|
[c5 a4 a4 ~]
|
||||||
.slow(2)
|
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||||
.legato(sine.range(0.3, 2).slow(28))
|
[e5 [~ c5] e5 [d5 c5]]
|
||||||
.wave("sawtooth square".fast(2))
|
[b4 [b4 c5] d5 e5]
|
||||||
.filter('lowpass', cosine.range(500,4000).slow(16))
|
[c5 a4 a4 ~]],
|
||||||
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})
|
[[e2 e3]*4]
|
||||||
|
[[a2 a3]*4]
|
||||||
|
[[g#2 g#3]*2 [e2 e3]*2]
|
||||||
|
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||||
|
[[d2 d3]*4]
|
||||||
|
[[c2 c3]*4]
|
||||||
|
[[b1 b2]*2 [e2 e3]*2]
|
||||||
|
[[a1 a2]*4]`).slow(16)
|
||||||
-->
|
-->
|
||||||
</strudel-repl>
|
</strudel-repl>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/embed",
|
"name": "@strudel.cycles/embed",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
||||||
"main": "embed.js",
|
"main": "embed.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ npm i @strudel.cycles/eval --save
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
<!-- TODO: -extend +evalScope -->
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { evaluate, extend } from '@strudel.cycles/eval';
|
import { evalScope } from '@strudel.cycles/core';
|
||||||
|
import { evaluate } from '@strudel.cycles/eval';
|
||||||
|
|
||||||
evalScope(
|
evalScope(
|
||||||
import('@strudel.cycles/core'),
|
import('@strudel.cycles/core'),
|
||||||
|
|||||||
@@ -4,46 +4,9 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
|||||||
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/>.
|
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 { evaluate as _evaluate } from '@strudel.cycles/core';
|
||||||
import shapeshifter from './shapeshifter.mjs';
|
import shapeshifter from './shapeshifter.mjs';
|
||||||
import * as strudel from '@strudel.cycles/core';
|
|
||||||
|
|
||||||
const { isPattern, Pattern } = strudel;
|
|
||||||
|
|
||||||
export const extend = (...args) => {
|
|
||||||
console.warn('@strudel.cycles/eval extend is deprecated, please use evalScope instead');
|
|
||||||
Object.assign(globalThis, ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
let scoped = false;
|
|
||||||
export const evalScope = async (...args) => {
|
|
||||||
if (scoped) {
|
|
||||||
console.warn('@strudel.cycles/eval evalScope was called more than once.');
|
|
||||||
}
|
|
||||||
scoped = true;
|
|
||||||
const results = await Promise.allSettled(args);
|
|
||||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
|
||||||
results.forEach((result, i) => {
|
|
||||||
if (result.status === 'rejected') {
|
|
||||||
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
|
|
||||||
};
|
|
||||||
|
|
||||||
function safeEval(str) {
|
|
||||||
return Function('"use strict";return (' + str + ')')();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const evaluate = async (code) => {
|
export const evaluate = async (code) => {
|
||||||
if (!scoped) {
|
return _evaluate(code, shapeshifter);
|
||||||
await evalScope(); // at least scope Pattern.prototype.boostrap
|
|
||||||
}
|
|
||||||
const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code
|
|
||||||
let evaluated = await safeEval(shapeshifted);
|
|
||||||
if (!isPattern(evaluated)) {
|
|
||||||
console.log('evaluated', evaluated);
|
|
||||||
const message = `got "${typeof evaluated}" instead of pattern`;
|
|
||||||
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
|
||||||
}
|
|
||||||
return { mode: 'javascript', pattern: evaluated };
|
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/eval",
|
"name": "@strudel.cycles/eval",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/eval",
|
"name": "@strudel.cycles/eval",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "Code evaluator for strudel",
|
"description": "Code evaluator for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.2.0",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"estraverse": "^5.3.0",
|
"estraverse": "^5.3.0",
|
||||||
"shift-ast": "^6.1.0",
|
"shift-ast": "^7.0.0",
|
||||||
"shift-codegen": "^7.0.3",
|
"shift-codegen": "^8.1.0",
|
||||||
"shift-parser": "^7.0.3",
|
"shift-parser": "^8.0.0",
|
||||||
"shift-spec": "^2018.0.2",
|
"shift-spec": "^2019.0.0",
|
||||||
"shift-traverser": "^1.0.0"
|
"shift-traverser": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name);
|
|||||||
const addLocations = true;
|
const addLocations = true;
|
||||||
export const addMiniLocations = true;
|
export const addMiniLocations = true;
|
||||||
export const minifyStrings = true;
|
export const minifyStrings = true;
|
||||||
export const wrappedAsync = true;
|
export const wrappedAsync = false; // this is now handled by core evaluate by default
|
||||||
|
export const shouldAddReturn = true;
|
||||||
|
|
||||||
export default (_code) => {
|
export default (_code) => {
|
||||||
const { code, addReturn } = wrapAsync(_code);
|
const { code, addReturn } = wrapAsync(_code);
|
||||||
@@ -125,7 +126,7 @@ export default (_code) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// add return to last statement (because it's wrapped in an async function artificially)
|
// add return to last statement (because it's wrapped in an async function artificially)
|
||||||
if (wrappedAsync) {
|
if (shouldAddReturn) {
|
||||||
addReturn(shifted);
|
addReturn(shifted);
|
||||||
}
|
}
|
||||||
const generated = undisguiseImports(codegen(shifted));
|
const generated = undisguiseImports(codegen(shifted));
|
||||||
@@ -153,7 +154,7 @@ ${code}
|
|||||||
})()`;
|
})()`;
|
||||||
}
|
}
|
||||||
const addReturn = (ast) => {
|
const addReturn = (ast) => {
|
||||||
const body = ast.statements[0].expression.callee.body; // actual code ast inside async function body
|
const body = wrappedAsync ? ast.statements[0].expression.callee.body : ast;
|
||||||
body.statements = body.statements
|
body.statements = body.statements
|
||||||
.slice(0, -1)
|
.slice(0, -1)
|
||||||
.concat([new ReturnStatement({ expression: body.statements.slice(-1)[0] })]);
|
.concat([new ReturnStatement({ expression: body.statements.slice(-1)[0] })]);
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
import { expect, describe, it } from 'vitest';
|
import { expect, describe, it } from 'vitest';
|
||||||
|
|
||||||
import { evaluate, evalScope } from '../evaluate.mjs';
|
import { evaluate } from '../evaluate.mjs';
|
||||||
import { mini } from '@strudel.cycles/mini';
|
import { mini } from '@strudel.cycles/mini';
|
||||||
import * as strudel from '@strudel.cycles/core';
|
import * as strudel from '@strudel.cycles/core';
|
||||||
const { fastcat } = strudel;
|
const { fastcat, evalScope } = strudel;
|
||||||
|
|
||||||
describe('evaluate', async () => {
|
describe('evaluate', async () => {
|
||||||
await evalScope({ mini }, strudel);
|
await evalScope({ mini }, strudel);
|
||||||
|
|||||||
@@ -5,15 +5,21 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import shapeshifter from '../shapeshifter.mjs';
|
import shapeshifter, { wrappedAsync } from '../shapeshifter.mjs';
|
||||||
|
|
||||||
describe('shapeshifter', () => {
|
describe('shapeshifter', () => {
|
||||||
it('Should shift simple double quote string', () => {
|
it('Should shift simple double quote string', () => {
|
||||||
expect(shapeshifter('"c3"')).toEqual('(async()=>{return mini("c3").withMiniLocation([1,0,15],[1,4,19])})()');
|
if (wrappedAsync) {
|
||||||
});
|
expect(shapeshifter('"c3"')).toEqual('(async()=>{return mini("c3").withMiniLocation([1,0,15],[1,4,19])})()');
|
||||||
it('Should handle dynamic imports', () => {
|
} else {
|
||||||
expect(shapeshifter('const { default: foo } = await import(\'https://bar.com/foo.js\');"c3"')).toEqual(
|
expect(shapeshifter('"c3"')).toEqual('return mini("c3").withMiniLocation([1,0,0],[1,4,4])');
|
||||||
'(async()=>{const{default:foo}=await import("https://bar.com/foo.js");return mini("c3").withMiniLocation([1,64,79],[1,68,83])})()',
|
}
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
if (wrappedAsync) {
|
||||||
|
it('Should handle dynamic imports', () => {
|
||||||
|
expect(shapeshifter('const { default: foo } = await import(\'https://bar.com/foo.js\');"c3"')).toEqual(
|
||||||
|
'const{default:foo}=await import("https://bar.com/foo.js");return mini("c3").withMiniLocation([1,64,79],[1,68,83])',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { isNote } from 'tone';
|
import { isNote } from 'tone';
|
||||||
import _WebMidi from 'webmidi';
|
import * as _WebMidi from 'webmidi';
|
||||||
import { Pattern, isPattern } from '@strudel.cycles/core';
|
import { Pattern, isPattern } from '@strudel.cycles/core';
|
||||||
import { Tone } from '@strudel.cycles/tone';
|
import { Tone } from '@strudel.cycles/tone';
|
||||||
|
|
||||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||||
export const WebMidi = _WebMidi;
|
export const { WebMidi } = _WebMidi;
|
||||||
|
|
||||||
export function enableWebMidi() {
|
export function enableWebMidi() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/midi",
|
"name": "@strudel.cycles/midi",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/midi",
|
"name": "@strudel.cycles/midi",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "Midi API for strudel",
|
"description": "Midi API for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/tone": "^0.2.0",
|
"@strudel.cycles/tone": "^0.3.2",
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
"webmidi": "^2.5.2"
|
"webmidi": "^3.0.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,3 +33,12 @@ yields:
|
|||||||
## Mini Notation API
|
## Mini Notation API
|
||||||
|
|
||||||
See "Mini Notation" in the [Strudel Tutorial](https://strudel.tidalcycles.org/tutorial/)
|
See "Mini Notation" in the [Strudel Tutorial](https://strudel.tidalcycles.org/tutorial/)
|
||||||
|
|
||||||
|
## Building the Parser
|
||||||
|
|
||||||
|
The parser [krill-parser.js] is generated from [krill.pegjs](./krill.pegjs) using [peggy](https://peggyjs.org/).
|
||||||
|
To generate the parser, run
|
||||||
|
|
||||||
|
```js
|
||||||
|
npm run build:parser
|
||||||
|
```
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
import * as krill from './krill-parser.js';
|
import * as krill from './krill-parser.js';
|
||||||
import * as strudel from '@strudel.cycles/core';
|
import * as strudel from '@strudel.cycles/core';
|
||||||
import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs';
|
// import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs';
|
||||||
|
|
||||||
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;
|
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;
|
||||||
|
|
||||||
@@ -29,7 +29,10 @@ const applyOptions = (parent) => (pat, i) => {
|
|||||||
case 'bjorklund':
|
case 'bjorklund':
|
||||||
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
|
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
|
||||||
case 'degradeBy':
|
case 'degradeBy':
|
||||||
return reify(pat)._degradeByWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), operator.arguments_.amount);
|
return reify(pat)._degradeByWith(
|
||||||
|
strudel.rand.early(randOffset * _nextSeed()).segment(1),
|
||||||
|
operator.arguments_.amount,
|
||||||
|
);
|
||||||
// TODO: case 'fixed-step': "%"
|
// TODO: case 'fixed-step': "%"
|
||||||
}
|
}
|
||||||
console.warn(`operator "${operator.type_}" not implemented`);
|
console.warn(`operator "${operator.type_}" not implemented`);
|
||||||
@@ -112,9 +115,9 @@ export function patternifyAST(ast) {
|
|||||||
return silence;
|
return silence;
|
||||||
}
|
}
|
||||||
if (typeof ast.source_ !== 'object') {
|
if (typeof ast.source_ !== 'object') {
|
||||||
if (!addMiniLocations) {
|
/* if (!addMiniLocations) {
|
||||||
return ast.source_;
|
return ast.source_;
|
||||||
}
|
} */
|
||||||
if (!ast.location_) {
|
if (!ast.location_) {
|
||||||
console.warn('no location for', ast);
|
console.warn('no location for', ast);
|
||||||
return ast.source_;
|
return ast.source_;
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/mini",
|
"name": "@strudel.cycles/mini",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/mini",
|
"name": "@strudel.cycles/mini",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "Mini notation for strudel",
|
"description": "Mini notation for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.2.0",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/eval": "^0.2.0",
|
"@strudel.cycles/eval": "^0.3.2",
|
||||||
"@strudel.cycles/tone": "^0.2.0"
|
"@strudel.cycles/tone": "^0.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"peggy": "^2.0.1"
|
"peggy": "^2.0.1"
|
||||||
|
|||||||
@@ -35,3 +35,5 @@ s("<bd sd> hh").osc()
|
|||||||
```
|
```
|
||||||
|
|
||||||
or just [click here](http://localhost:3000/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
|
or just [click here](http://localhost:3000/#cygiPGJkIHNkPiBoaCIpLm9zYygp)...
|
||||||
|
|
||||||
|
You can read more about [how to use Superdirt with Strudel the Tutorial](https://strudel.tidalcycles.org/tutorial/#superdirt-api)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import OSC from 'osc-js';
|
import OSC from 'osc-js';
|
||||||
import { Pattern } from '@strudel.cycles/core';
|
import { parseNumeral, Pattern } from '@strudel.cycles/core';
|
||||||
|
|
||||||
const comm = new OSC();
|
const comm = new OSC();
|
||||||
comm.open();
|
comm.open();
|
||||||
@@ -31,6 +31,10 @@ Pattern.prototype.osc = function () {
|
|||||||
startedAt = Date.now() - currentTime * 1000;
|
startedAt = Date.now() - currentTime * 1000;
|
||||||
}
|
}
|
||||||
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
||||||
|
// make sure n and note are numbers
|
||||||
|
controls.n && (controls.n = parseNumeral(controls.n));
|
||||||
|
controls.note && (controls.note = parseNumeral(controls.note));
|
||||||
|
|
||||||
const keyvals = Object.entries(controls).flat();
|
const keyvals = Object.entries(controls).flat();
|
||||||
const ts = Math.floor(startedAt + (time + latency) * 1000);
|
const ts = Math.floor(startedAt + (time + latency) * 1000);
|
||||||
const message = new OSC.Message('/dirt/play', ...keyvals);
|
const message = new OSC.Message('/dirt/play', ...keyvals);
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/osc",
|
"name": "@strudel.cycles/osc",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/osc",
|
"name": "@strudel.cycles/osc",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"description": "OSC messaging for strudel",
|
"description": "OSC messaging for strudel",
|
||||||
"main": "osc.mjs",
|
"main": "osc.mjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -30,6 +30,6 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"osc-js": "^2.3.2"
|
"osc-js": "^2.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
examples
|
||||||
@@ -1,4 +1,43 @@
|
|||||||
# @strudel.cycles/react
|
# @strudel.cycles/react
|
||||||
|
|
||||||
This package contains react hooks and components for strudel.
|
This package contains react hooks and components for strudel. It is used internally by the Strudel REPL.
|
||||||
Example coming soon
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```js
|
||||||
|
npm i @strudel.cycles/react
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Here is a minimal example of how to set up a MiniRepl:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { evalScope, controls } from '@strudel.cycles/core';
|
||||||
|
import { MiniRepl } from '@strudel.cycles/react';
|
||||||
|
import { prebake } from '../repl/src/prebake.mjs';
|
||||||
|
|
||||||
|
evalScope(
|
||||||
|
controls,
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
import('@strudel.cycles/tonal'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
/* probably import other strudel packages */
|
||||||
|
);
|
||||||
|
|
||||||
|
prebake();
|
||||||
|
|
||||||
|
export function Repl({ tune }) {
|
||||||
|
return <MiniRepl tune={tune} hideOutsideView={true} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
If you change something in here and want to see the changes in the repl, make sure to run `npm run build` inside this folder!
|
||||||
|
|
||||||
|
```js
|
||||||
|
npm run dev # dev server
|
||||||
|
npm run build # build package
|
||||||
|
```
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+387
-501
@@ -1,582 +1,468 @@
|
|||||||
import React, { useCallback, useState, useEffect, useMemo, useRef, useLayoutEffect } from 'react';
|
import y, { useCallback as T, useState as w, useEffect as q, useMemo as R, useRef as I, useLayoutEffect as j } from "react";
|
||||||
import _CodeMirror from '@uiw/react-codemirror';
|
import Y from "@uiw/react-codemirror";
|
||||||
import { Decoration, EditorView } from '@codemirror/view';
|
import { Decoration as A, EditorView as K } from "@codemirror/view";
|
||||||
import { StateEffect, StateField } from '@codemirror/state';
|
import { StateEffect as U, StateField as Q } from "@codemirror/state";
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript as Z } from "@codemirror/lang-javascript";
|
||||||
import { tags } from '@lezer/highlight';
|
import { tags as m } from "@lezer/highlight";
|
||||||
import { createTheme } from '@uiw/codemirror-themes';
|
import { createTheme as ee } from "@uiw/codemirror-themes";
|
||||||
import { useInView } from 'react-hook-inview';
|
import { useInView as te } from "react-hook-inview";
|
||||||
import { evaluate } from '@strudel.cycles/eval';
|
import { evaluate as G } from "@strudel.cycles/eval";
|
||||||
import { Tone } from '@strudel.cycles/tone';
|
import { Tone as M } from "@strudel.cycles/tone";
|
||||||
import { TimeSpan, State } from '@strudel.cycles/core';
|
import { TimeSpan as oe, State as re } from "@strudel.cycles/core";
|
||||||
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
|
import { webaudioOutputTrigger as ne } from "@strudel.cycles/webaudio";
|
||||||
import { WebMidi, enableWebMidi } from '@strudel.cycles/midi';
|
import { WebMidi as N, enableWebMidi as se } from "@strudel.cycles/midi";
|
||||||
|
const ae = ee({
|
||||||
var strudelTheme = createTheme({
|
theme: "dark",
|
||||||
theme: 'dark',
|
|
||||||
settings: {
|
settings: {
|
||||||
background: '#222',
|
background: "#222",
|
||||||
foreground: '#75baff', // whats that?
|
foreground: "#75baff",
|
||||||
caret: '#ffcc00',
|
caret: "#ffcc00",
|
||||||
selection: 'rgba(128, 203, 196, 0.5)',
|
selection: "rgba(128, 203, 196, 0.5)",
|
||||||
selectionMatch: '#036dd626',
|
selectionMatch: "#036dd626",
|
||||||
lineHighlight: '#8a91991a',
|
lineHighlight: "#8a91991a",
|
||||||
gutterBackground: 'transparent',
|
gutterBackground: "transparent",
|
||||||
// gutterForeground: '#8a919966',
|
gutterForeground: "#676e95"
|
||||||
gutterForeground: '#676e95',
|
|
||||||
},
|
},
|
||||||
styles: [
|
styles: [
|
||||||
{ tag: tags.keyword, color: '#c792ea' },
|
{ tag: m.keyword, color: "#c792ea" },
|
||||||
{ tag: tags.operator, color: '#89ddff' },
|
{ tag: m.operator, color: "#89ddff" },
|
||||||
{ tag: tags.special(tags.variableName), color: '#eeffff' },
|
{ tag: m.special(m.variableName), color: "#eeffff" },
|
||||||
{ tag: tags.typeName, color: '#f07178' },
|
{ tag: m.typeName, color: "#f07178" },
|
||||||
{ tag: tags.atom, color: '#f78c6c' },
|
{ tag: m.atom, color: "#f78c6c" },
|
||||||
{ tag: tags.number, color: '#ff5370' },
|
{ tag: m.number, color: "#ff5370" },
|
||||||
{ tag: tags.definition(tags.variableName), color: '#82aaff' },
|
{ tag: m.definition(m.variableName), color: "#82aaff" },
|
||||||
{ tag: tags.string, color: '#c3e88d' },
|
{ tag: m.string, color: "#c3e88d" },
|
||||||
{ tag: tags.special(tags.string), color: '#f07178' },
|
{ tag: m.special(m.string), color: "#f07178" },
|
||||||
{ tag: tags.comment, color: '#7d8799' },
|
{ tag: m.comment, color: "#7d8799" },
|
||||||
{ tag: tags.variableName, color: '#f07178' },
|
{ tag: m.variableName, color: "#f07178" },
|
||||||
{ tag: tags.tagName, color: '#ff5370' },
|
{ tag: m.tagName, color: "#ff5370" },
|
||||||
{ tag: tags.bracket, color: '#a2a1a4' },
|
{ tag: m.bracket, color: "#a2a1a4" },
|
||||||
{ tag: tags.meta, color: '#ffcb6b' },
|
{ tag: m.meta, color: "#ffcb6b" },
|
||||||
{ tag: tags.attributeName, color: '#c792ea' },
|
{ tag: m.attributeName, color: "#c792ea" },
|
||||||
{ tag: tags.propertyName, color: '#c792ea' },
|
{ tag: m.propertyName, color: "#c792ea" },
|
||||||
{ tag: tags.className, color: '#decb6b' },
|
{ tag: m.className, color: "#decb6b" },
|
||||||
{ tag: tags.invalid, color: '#ffffff' },
|
{ tag: m.invalid, color: "#ffffff" }
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
const z = U.define(), ce = Q.define({
|
||||||
var style = '';
|
|
||||||
|
|
||||||
const setFlash = StateEffect.define();
|
|
||||||
const flashField = StateField.define({
|
|
||||||
create() {
|
create() {
|
||||||
return Decoration.none;
|
return A.none;
|
||||||
},
|
},
|
||||||
update(flash2, tr) {
|
update(e, o) {
|
||||||
try {
|
try {
|
||||||
for (let e of tr.effects) {
|
for (let r of o.effects)
|
||||||
if (e.is(setFlash)) {
|
if (r.is(z))
|
||||||
if (e.value) {
|
if (r.value) {
|
||||||
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } });
|
const n = A.mark({ attributes: { style: "background-color: #FFCA2880" } });
|
||||||
flash2 = Decoration.set([mark.range(0, tr.newDoc.length)]);
|
e = A.set([n.range(0, o.newDoc.length)]);
|
||||||
} else {
|
} else
|
||||||
flash2 = Decoration.set([]);
|
e = A.set([]);
|
||||||
}
|
return e;
|
||||||
}
|
} catch (r) {
|
||||||
}
|
return console.warn("flash error", r), e;
|
||||||
return flash2;
|
|
||||||
} catch (err) {
|
|
||||||
console.warn("flash error", err);
|
|
||||||
return flash2;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
provide: (f) => EditorView.decorations.from(f)
|
provide: (e) => K.decorations.from(e)
|
||||||
});
|
}), ie = (e) => {
|
||||||
const flash = (view) => {
|
e.dispatch({ effects: z.of(!0) }), setTimeout(() => {
|
||||||
view.dispatch({ effects: setFlash.of(true) });
|
e.dispatch({ effects: z.of(!1) });
|
||||||
setTimeout(() => {
|
|
||||||
view.dispatch({ effects: setFlash.of(false) });
|
|
||||||
}, 200);
|
}, 200);
|
||||||
};
|
}, O = U.define(), le = Q.define({
|
||||||
const setHighlights = StateEffect.define();
|
|
||||||
const highlightField = StateField.define({
|
|
||||||
create() {
|
create() {
|
||||||
return Decoration.none;
|
return A.none;
|
||||||
},
|
},
|
||||||
update(highlights, tr) {
|
update(e, o) {
|
||||||
try {
|
try {
|
||||||
for (let e of tr.effects) {
|
for (let r of o.effects)
|
||||||
if (e.is(setHighlights)) {
|
if (r.is(O)) {
|
||||||
const marks = e.value.map(
|
const n = r.value.map(
|
||||||
(hap) => (hap.context.locations || []).map(({ start, end }) => {
|
(s) => (s.context.locations || []).map(({ start: i, end: a }) => {
|
||||||
const color = hap.context.color || "#FFCA28";
|
const t = s.context.color || "#FFCA28";
|
||||||
let from = tr.newDoc.line(start.line).from + start.column;
|
let l = o.newDoc.line(i.line).from + i.column, c = o.newDoc.line(a.line).from + a.column;
|
||||||
let to = tr.newDoc.line(end.line).from + end.column;
|
const f = o.newDoc.length;
|
||||||
const l = tr.newDoc.length;
|
return l > f || c > f ? void 0 : A.mark({ attributes: { style: `outline: 1.5px solid ${t};` } }).range(l, c);
|
||||||
if (from > l || to > l) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const mark = Decoration.mark({ attributes: { style: `outline: 1.5px solid ${color};` } });
|
|
||||||
return mark.range(from, to);
|
|
||||||
})
|
})
|
||||||
).flat().filter(Boolean) || [];
|
).flat().filter(Boolean) || [];
|
||||||
highlights = Decoration.set(marks, true);
|
e = A.set(n, !0);
|
||||||
}
|
}
|
||||||
}
|
return e;
|
||||||
return highlights;
|
} catch {
|
||||||
} catch (err) {
|
return A.set([]);
|
||||||
return Decoration.set([]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
provide: (f) => EditorView.decorations.from(f)
|
provide: (e) => K.decorations.from(e)
|
||||||
});
|
}), ue = [Z(), ae, le, ce];
|
||||||
const extensions = [javascript(), strudelTheme, highlightField, flashField];
|
function de({ value: e, onChange: o, onViewChanged: r, onSelectionChange: n, options: s, editorDidMount: i }) {
|
||||||
function CodeMirror({ value, onChange, onViewChanged, onSelectionChange, options, editorDidMount }) {
|
const a = T(
|
||||||
const handleOnChange = useCallback(
|
(c) => {
|
||||||
(value2) => {
|
o?.(c);
|
||||||
onChange?.(value2);
|
|
||||||
},
|
},
|
||||||
[onChange]
|
[o]
|
||||||
);
|
), t = T(
|
||||||
const handleOnCreateEditor = useCallback(
|
(c) => {
|
||||||
(view) => {
|
r?.(c);
|
||||||
onViewChanged?.(view);
|
|
||||||
},
|
},
|
||||||
[onViewChanged]
|
[r]
|
||||||
);
|
), l = T(
|
||||||
const handleOnUpdate = useCallback(
|
(c) => {
|
||||||
(viewUpdate) => {
|
c.selectionSet && n && n?.(c.state.selection);
|
||||||
if (viewUpdate.selectionSet && onSelectionChange) {
|
|
||||||
onSelectionChange?.(viewUpdate.state.selection);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[onSelectionChange]
|
[n]
|
||||||
);
|
);
|
||||||
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(_CodeMirror, {
|
return /* @__PURE__ */ y.createElement(y.Fragment, null, /* @__PURE__ */ y.createElement(Y, {
|
||||||
value,
|
value: e,
|
||||||
onChange: handleOnChange,
|
onChange: a,
|
||||||
onCreateEditor: handleOnCreateEditor,
|
onCreateEditor: t,
|
||||||
onUpdate: handleOnUpdate,
|
onUpdate: l,
|
||||||
extensions
|
extensions: ue
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
function fe(e) {
|
||||||
/*
|
const { onEvent: o, onQuery: r, onSchedule: n, ready: s = !0, onDraw: i } = e, [a, t] = w(!1), l = 1, c = () => Math.floor(M.getTransport().seconds / l), f = (g = c()) => {
|
||||||
useCycle.mjs - <short description TODO>
|
const C = new oe(g, g + 1), v = r?.(new re(C)) || [];
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/useCycle.mjs>
|
n?.(v, g);
|
||||||
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/>.
|
const h = C.begin.valueOf();
|
||||||
*/
|
M.getTransport().cancel(h);
|
||||||
|
const D = (g + 1) * l - 0.5, _ = Math.max(M.getTransport().seconds, D) + 0.1;
|
||||||
/* export declare interface UseCycleProps {
|
M.getTransport().schedule(() => {
|
||||||
onEvent: ToneEventCallback<any>;
|
f(g + 1);
|
||||||
onQuery?: (state: State) => Hap[];
|
}, _), v?.filter((E) => E.part.begin.equals(E.whole?.begin)).forEach((E) => {
|
||||||
onSchedule?: (events: Hap[], cycle: number) => void;
|
M.getTransport().schedule((L) => {
|
||||||
onDraw?: ToneEventCallback<any>;
|
o(L, E, M.getContext().currentTime), M.Draw.schedule(() => {
|
||||||
ready?: boolean; // if false, query will not be called on change props
|
i?.(L, E);
|
||||||
} */
|
}, L);
|
||||||
|
}, E.part.begin.valueOf());
|
||||||
// function useCycle(props: UseCycleProps) {
|
});
|
||||||
function useCycle(props) {
|
|
||||||
// onX must use useCallback!
|
|
||||||
const { onEvent, onQuery, onSchedule, ready = true, onDraw } = props;
|
|
||||||
const [started, setStarted] = useState(false);
|
|
||||||
const cycleDuration = 1;
|
|
||||||
const activeCycle = () => Math.floor(Tone.getTransport().seconds / cycleDuration);
|
|
||||||
|
|
||||||
// pull events with onQuery + count up to next cycle
|
|
||||||
const query = (cycle = activeCycle()) => {
|
|
||||||
const timespan = new TimeSpan(cycle, cycle + 1);
|
|
||||||
const events = onQuery?.(new State(timespan)) || [];
|
|
||||||
onSchedule?.(events, cycle);
|
|
||||||
// cancel events after current query. makes sure no old events are player for rescheduled cycles
|
|
||||||
// console.log('schedule', cycle);
|
|
||||||
// query next cycle in the middle of the current
|
|
||||||
const cancelFrom = timespan.begin.valueOf();
|
|
||||||
Tone.getTransport().cancel(cancelFrom);
|
|
||||||
// const queryNextTime = (cycle + 1) * cycleDuration - 0.1;
|
|
||||||
const queryNextTime = (cycle + 1) * cycleDuration - 0.5;
|
|
||||||
|
|
||||||
// if queryNextTime would be before current time, execute directly (+0.1 for safety that it won't miss)
|
|
||||||
const t = Math.max(Tone.getTransport().seconds, queryNextTime) + 0.1;
|
|
||||||
Tone.getTransport().schedule(() => {
|
|
||||||
query(cycle + 1);
|
|
||||||
}, t);
|
|
||||||
|
|
||||||
// schedule events for next cycle
|
|
||||||
events
|
|
||||||
?.filter((event) => event.part.begin.equals(event.whole?.begin))
|
|
||||||
.forEach((event) => {
|
|
||||||
Tone.getTransport().schedule((time) => {
|
|
||||||
onEvent(time, event, Tone.getContext().currentTime);
|
|
||||||
Tone.Draw.schedule(() => {
|
|
||||||
// do drawing or DOM manipulation here
|
|
||||||
onDraw?.(time, event);
|
|
||||||
}, time);
|
|
||||||
}, event.part.begin.valueOf());
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
q(() => {
|
||||||
useEffect(() => {
|
s && f();
|
||||||
ready && query();
|
}, [o, n, r, i, s]);
|
||||||
}, [onEvent, onSchedule, onQuery, onDraw, ready]);
|
const p = async () => {
|
||||||
|
t(!0), await M.start(), M.getTransport().start("+0.1");
|
||||||
const start = async () => {
|
}, d = () => {
|
||||||
setStarted(true);
|
M.getTransport().pause(), t(!1);
|
||||||
await Tone.start();
|
|
||||||
Tone.getTransport().start('+0.1');
|
|
||||||
};
|
};
|
||||||
const stop = () => {
|
|
||||||
Tone.getTransport().pause();
|
|
||||||
setStarted(false);
|
|
||||||
};
|
|
||||||
const toggle = () => (started ? stop() : start());
|
|
||||||
return {
|
return {
|
||||||
start,
|
start: p,
|
||||||
stop,
|
stop: d,
|
||||||
onEvent,
|
onEvent: o,
|
||||||
started,
|
started: a,
|
||||||
setStarted,
|
setStarted: t,
|
||||||
toggle,
|
toggle: () => a ? d() : p(),
|
||||||
query,
|
query: f,
|
||||||
activeCycle,
|
activeCycle: c
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function ge(e) {
|
||||||
/*
|
return q(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), T((o) => window.postMessage(o, "*"), []);
|
||||||
usePostMessage.mjs - <short description TODO>
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/usePostMessage.mjs>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function usePostMessage(listener) {
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('message', listener);
|
|
||||||
return () => window.removeEventListener('message', listener);
|
|
||||||
}, [listener]);
|
|
||||||
return useCallback((data) => window.postMessage(data, '*'), []);
|
|
||||||
}
|
}
|
||||||
|
let he = () => Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
||||||
|
const me = (e) => encodeURIComponent(btoa(e));
|
||||||
|
function pe({ tune: e, autolink: o = !0, onEvent: r, onDraw: n }) {
|
||||||
|
const s = R(() => he(), []), [i, a] = w(e), [t, l] = w(), [c, f] = w(""), [p, d] = w(), [k, g] = w(!1), [C, v] = w(""), [h, D] = w(), _ = R(() => i !== t || p, [i, t, p]), E = T((b) => f((u) => u + `${u ? `
|
||||||
|
|
||||||
/*
|
` : ""}${b}`), []), L = R(() => {
|
||||||
useRepl.mjs - <short description TODO>
|
if (t && !t.includes("strudel disable-highlighting"))
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/useRepl.mjs>
|
return (b, u) => n?.(b, u, t);
|
||||||
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/>.
|
}, [t, n]), H = R(() => t && t.includes("strudel hide-header"), [t]), x = R(() => t && t.includes("strudel hide-console"), [t]), P = fe({
|
||||||
*/
|
onDraw: L,
|
||||||
|
onEvent: T(
|
||||||
let s4 = () => {
|
(b, u, X) => {
|
||||||
return Math.floor((1 + Math.random()) * 0x10000)
|
|
||||||
.toString(16)
|
|
||||||
.substring(1);
|
|
||||||
};
|
|
||||||
const generateHash = (code) => encodeURIComponent(btoa(code));
|
|
||||||
|
|
||||||
function useRepl({ tune, autolink = true, onEvent, onDraw: onDrawProp }) {
|
|
||||||
const id = useMemo(() => s4(), []);
|
|
||||||
const [code, setCode] = useState(tune);
|
|
||||||
const [activeCode, setActiveCode] = useState();
|
|
||||||
const [log, setLog] = useState('');
|
|
||||||
const [error, setError] = useState();
|
|
||||||
const [pending, setPending] = useState(false);
|
|
||||||
const [hash, setHash] = useState('');
|
|
||||||
const [pattern, setPattern] = useState();
|
|
||||||
const dirty = useMemo(() => code !== activeCode || error, [code, activeCode, error]);
|
|
||||||
const pushLog = useCallback((message) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`), []);
|
|
||||||
|
|
||||||
// below block allows disabling the highlighting by including "strudel disable-highlighting" in the code (as comment)
|
|
||||||
const onDraw = useMemo(() => {
|
|
||||||
if (activeCode && !activeCode.includes('strudel disable-highlighting')) {
|
|
||||||
return (time, event) => onDrawProp?.(time, event, activeCode);
|
|
||||||
}
|
|
||||||
}, [activeCode, onDrawProp]);
|
|
||||||
|
|
||||||
const hideHeader = useMemo(() => activeCode && activeCode.includes('strudel hide-header'), [activeCode]);
|
|
||||||
const hideConsole = useMemo(() => activeCode && activeCode.includes('strudel hide-console'), [activeCode]);
|
|
||||||
// cycle hook to control scheduling
|
|
||||||
const cycle = useCycle({
|
|
||||||
onDraw,
|
|
||||||
onEvent: useCallback(
|
|
||||||
(time, event, currentTime) => {
|
|
||||||
try {
|
try {
|
||||||
onEvent?.(event);
|
r?.(u), u.context.logs?.length && u.context.logs.forEach(E);
|
||||||
if (event.context.logs?.length) {
|
const { onTrigger: S = ne } = u.context;
|
||||||
event.context.logs.forEach(pushLog);
|
S(b, u, X, 1);
|
||||||
}
|
} catch (S) {
|
||||||
const { onTrigger = webaudioOutputTrigger } = event.context;
|
console.warn(S), S.message = "unplayable event: " + S?.message, E(S.message);
|
||||||
onTrigger(time, event, currentTime, 1 /* cps */);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err);
|
|
||||||
err.message = 'unplayable event: ' + err?.message;
|
|
||||||
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onEvent, pushLog],
|
[r, E]
|
||||||
),
|
),
|
||||||
onQuery: useCallback(
|
onQuery: T(
|
||||||
(state) => {
|
(b) => {
|
||||||
try {
|
try {
|
||||||
return pattern?.query(state) || [];
|
return h?.query(b) || [];
|
||||||
} catch (err) {
|
} catch (u) {
|
||||||
console.warn(err);
|
return console.warn(u), u.message = "query error: " + u.message, d(u), [];
|
||||||
err.message = 'query error: ' + err.message;
|
|
||||||
setError(err);
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pattern],
|
[h]
|
||||||
),
|
),
|
||||||
onSchedule: useCallback((_events, cycle) => logCycle(_events), []),
|
onSchedule: T((b, u) => J(b), []),
|
||||||
ready: !!pattern && !!activeCode,
|
ready: !!h && !!t
|
||||||
});
|
}), B = ge(({ data: { from: b, type: u } }) => {
|
||||||
|
u === "start" && b !== s && (P.setStarted(!1), l(void 0));
|
||||||
const broadcast = usePostMessage(({ data: { from, type } }) => {
|
}), V = T(
|
||||||
if (type === 'start' && from !== id) {
|
async (b = i) => {
|
||||||
// console.log('message', from, type);
|
if (t && !_) {
|
||||||
cycle.setStarted(false);
|
d(void 0), P.start();
|
||||||
setActiveCode(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const activateCode = useCallback(
|
|
||||||
async (_code = code) => {
|
|
||||||
if (activeCode && !dirty) {
|
|
||||||
setError(undefined);
|
|
||||||
cycle.start();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setPending(true);
|
g(!0);
|
||||||
const parsed = await evaluate(_code);
|
const u = await G(b);
|
||||||
cycle.start();
|
P.start(), B({ type: "start", from: s }), D(() => u.pattern), o && (window.location.hash = "#" + encodeURIComponent(btoa(i))), v(me(i)), d(void 0), l(b), g(!1);
|
||||||
broadcast({ type: 'start', from: id });
|
} catch (u) {
|
||||||
setPattern(() => parsed.pattern);
|
u.message = "evaluation error: " + u.message, console.warn(u), d(u);
|
||||||
if (autolink) {
|
|
||||||
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
|
||||||
}
|
|
||||||
setHash(generateHash(code));
|
|
||||||
setError(undefined);
|
|
||||||
setActiveCode(_code);
|
|
||||||
setPending(false);
|
|
||||||
} catch (err) {
|
|
||||||
err.message = 'evaluation error: ' + err.message;
|
|
||||||
console.warn(err);
|
|
||||||
setError(err);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeCode, dirty, code, cycle, autolink, id, broadcast],
|
[t, _, i, P, o, s, B]
|
||||||
);
|
), J = (b, u) => {
|
||||||
// logs events of cycle
|
b.length;
|
||||||
const logCycle = (_events, cycle) => {
|
|
||||||
if (_events.length) ;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePlay = () => {
|
|
||||||
if (!cycle.started) {
|
|
||||||
activateCode();
|
|
||||||
} else {
|
|
||||||
cycle.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hideHeader,
|
hideHeader: H,
|
||||||
hideConsole,
|
hideConsole: x,
|
||||||
pending,
|
pending: k,
|
||||||
code,
|
code: i,
|
||||||
setCode,
|
setCode: a,
|
||||||
pattern,
|
pattern: h,
|
||||||
error,
|
error: p,
|
||||||
cycle,
|
cycle: P,
|
||||||
setPattern,
|
setPattern: D,
|
||||||
dirty,
|
dirty: _,
|
||||||
log,
|
log: c,
|
||||||
togglePlay,
|
togglePlay: () => {
|
||||||
setActiveCode,
|
P.started ? P.stop() : V();
|
||||||
activateCode,
|
},
|
||||||
activeCode,
|
setActiveCode: l,
|
||||||
pushLog,
|
activateCode: V,
|
||||||
hash,
|
activeCode: t,
|
||||||
|
pushLog: E,
|
||||||
|
hash: C
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function $(...e) {
|
||||||
/*
|
return e.filter(Boolean).join(" ");
|
||||||
cx.js - <short description TODO>
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/cx.js>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function cx(...classes) {
|
|
||||||
// : Array<string | undefined>
|
|
||||||
return classes.filter(Boolean).join(' ');
|
|
||||||
}
|
}
|
||||||
|
function ye({ view: e, pattern: o, active: r, getTime: n }) {
|
||||||
let highlights = []; // actively highlighted events
|
const s = I([]), i = I();
|
||||||
let lastEnd;
|
q(() => {
|
||||||
|
if (e)
|
||||||
function useHighlighting({ view, pattern, active }) {
|
if (o && r) {
|
||||||
useEffect(() => {
|
let t = function() {
|
||||||
if (view) {
|
|
||||||
if (pattern && active) {
|
|
||||||
let frame = requestAnimationFrame(updateHighlights);
|
|
||||||
|
|
||||||
function updateHighlights() {
|
|
||||||
try {
|
try {
|
||||||
const audioTime = Tone.getTransport().seconds;
|
const l = n(), f = [Math.max(i.current || l, l - 1 / 10), l + 1 / 60];
|
||||||
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
|
i.current = l + 1 / 60, s.current = s.current.filter((d) => d.whole.end > l);
|
||||||
// see https://github.com/tidalcycles/strudel/issues/108
|
const p = o.queryArc(...f).filter((d) => d.hasOnset());
|
||||||
const begin = Math.max(lastEnd || audioTime, audioTime - 1 / 10);
|
s.current = s.current.concat(p), e.dispatch({ effects: O.of(s.current) });
|
||||||
const span = [begin, audioTime + 1 / 60];
|
} catch {
|
||||||
lastEnd = audioTime + 1 / 60;
|
e.dispatch({ effects: O.of([]) });
|
||||||
highlights = highlights.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
|
||||||
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
|
||||||
highlights = highlights.concat(haps); // add potential new onsets
|
|
||||||
view.dispatch({ effects: setHighlights.of(highlights) }); // highlight all still active + new active haps
|
|
||||||
} catch (err) {
|
|
||||||
// console.log('error in updateHighlights', err);
|
|
||||||
view.dispatch({ effects: setHighlights.of([]) });
|
|
||||||
}
|
}
|
||||||
frame = requestAnimationFrame(updateHighlights);
|
a = requestAnimationFrame(t);
|
||||||
}
|
}, a = requestAnimationFrame(t);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelAnimationFrame(frame);
|
cancelAnimationFrame(a);
|
||||||
};
|
};
|
||||||
} else {
|
} else
|
||||||
highlights = [];
|
s.current = [], e.dispatch({ effects: O.of([]) });
|
||||||
view.dispatch({ effects: setHighlights.of([]) });
|
}, [o, r, e]);
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [pattern, active, view]);
|
|
||||||
}
|
}
|
||||||
|
const be = "_container_3i85k_1", we = "_header_3i85k_5", ve = "_buttons_3i85k_9", Ee = "_button_3i85k_9", ke = "_buttonDisabled_3i85k_17", Ce = "_error_3i85k_21", Me = "_body_3i85k_25", F = {
|
||||||
var tailwind = '';
|
container: be,
|
||||||
|
header: we,
|
||||||
const container = "_container_3i85k_1";
|
buttons: ve,
|
||||||
const header = "_header_3i85k_5";
|
button: Ee,
|
||||||
const buttons = "_buttons_3i85k_9";
|
buttonDisabled: ke,
|
||||||
const button = "_button_3i85k_9";
|
error: Ce,
|
||||||
const buttonDisabled = "_buttonDisabled_3i85k_17";
|
body: Me
|
||||||
const error = "_error_3i85k_21";
|
|
||||||
const body = "_body_3i85k_25";
|
|
||||||
var styles = {
|
|
||||||
container: container,
|
|
||||||
header: header,
|
|
||||||
buttons: buttons,
|
|
||||||
button: button,
|
|
||||||
buttonDisabled: buttonDisabled,
|
|
||||||
error: error,
|
|
||||||
body: body
|
|
||||||
};
|
};
|
||||||
|
function W({ type: e }) {
|
||||||
function Icon({ type }) {
|
return /* @__PURE__ */ y.createElement("svg", {
|
||||||
return /* @__PURE__ */ React.createElement("svg", {
|
|
||||||
xmlns: "http://www.w3.org/2000/svg",
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
className: "sc-h-5 sc-w-5",
|
className: "sc-h-5 sc-w-5",
|
||||||
viewBox: "0 0 20 20",
|
viewBox: "0 0 20 20",
|
||||||
fill: "currentColor"
|
fill: "currentColor"
|
||||||
}, {
|
}, {
|
||||||
refresh: /* @__PURE__ */ React.createElement("path", {
|
refresh: /* @__PURE__ */ y.createElement("path", {
|
||||||
fillRule: "evenodd",
|
fillRule: "evenodd",
|
||||||
d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
|
d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
|
||||||
clipRule: "evenodd"
|
clipRule: "evenodd"
|
||||||
}),
|
}),
|
||||||
play: /* @__PURE__ */ React.createElement("path", {
|
play: /* @__PURE__ */ y.createElement("path", {
|
||||||
fillRule: "evenodd",
|
fillRule: "evenodd",
|
||||||
d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
|
d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
|
||||||
clipRule: "evenodd"
|
clipRule: "evenodd"
|
||||||
}),
|
}),
|
||||||
pause: /* @__PURE__ */ React.createElement("path", {
|
pause: /* @__PURE__ */ y.createElement("path", {
|
||||||
fillRule: "evenodd",
|
fillRule: "evenodd",
|
||||||
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
|
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
|
||||||
clipRule: "evenodd"
|
clipRule: "evenodd"
|
||||||
})
|
})
|
||||||
}[type]);
|
}[e]);
|
||||||
}
|
}
|
||||||
|
function Be({ tune: e, hideOutsideView: o = !1, init: r, onEvent: n, enableKeyboard: s }) {
|
||||||
function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableKeyboard }) {
|
const { code: i, setCode: a, pattern: t, activeCode: l, activateCode: c, evaluateOnly: f, error: p, cycle: d, dirty: k, togglePlay: g, stop: C } = pe({
|
||||||
const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } = useRepl({
|
tune: e,
|
||||||
tune,
|
autolink: !1,
|
||||||
autolink: false,
|
onEvent: n
|
||||||
onEvent
|
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
q(() => {
|
||||||
init && evaluateOnly();
|
r && f();
|
||||||
}, [tune, init]);
|
}, [e, r]);
|
||||||
const [view, setView] = useState();
|
const [v, h] = w(), [D, _] = te({
|
||||||
const [ref, isVisible] = useInView({
|
|
||||||
threshold: 0.01
|
threshold: 0.01
|
||||||
});
|
}), E = I(), L = R(() => ((_ || !o) && (E.current = !0), _ || E.current), [_, o]);
|
||||||
const wasVisible = useRef();
|
return ye({
|
||||||
const show = useMemo(() => {
|
view: v,
|
||||||
if (isVisible || !hideOutsideView) {
|
pattern: t,
|
||||||
wasVisible.current = true;
|
active: d.started && !l?.includes("strudel disable-highlighting"),
|
||||||
}
|
getTime: () => M.getTransport().seconds
|
||||||
return isVisible || wasVisible.current;
|
}), j(() => {
|
||||||
}, [isVisible, hideOutsideView]);
|
if (s) {
|
||||||
useHighlighting({ view, pattern, active: cycle.started && !activeCode?.includes("strudel disable-highlighting") });
|
const H = async (x) => {
|
||||||
useLayoutEffect(() => {
|
(x.ctrlKey || x.altKey) && (x.code === "Enter" ? (x.preventDefault(), ie(v), await c()) : x.code === "Period" && (d.stop(), x.preventDefault()));
|
||||||
if (enableKeyboard) {
|
|
||||||
const handleKeyPress = async (e) => {
|
|
||||||
if (e.ctrlKey || e.altKey) {
|
|
||||||
if (e.code === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
flash(view);
|
|
||||||
await activateCode();
|
|
||||||
} else if (e.code === "Period") {
|
|
||||||
cycle.stop();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
window.addEventListener("keydown", handleKeyPress, true);
|
return window.addEventListener("keydown", H, !0), () => window.removeEventListener("keydown", H, !0);
|
||||||
return () => window.removeEventListener("keydown", handleKeyPress, true);
|
|
||||||
}
|
}
|
||||||
}, [enableKeyboard, pattern, code, activateCode, cycle, view]);
|
}, [s, t, i, c, d, v]), /* @__PURE__ */ y.createElement("div", {
|
||||||
return /* @__PURE__ */ React.createElement("div", {
|
className: F.container,
|
||||||
className: styles.container,
|
ref: D
|
||||||
ref
|
}, /* @__PURE__ */ y.createElement("div", {
|
||||||
}, /* @__PURE__ */ React.createElement("div", {
|
className: F.header
|
||||||
className: styles.header
|
}, /* @__PURE__ */ y.createElement("div", {
|
||||||
}, /* @__PURE__ */ React.createElement("div", {
|
className: F.buttons
|
||||||
className: styles.buttons
|
}, /* @__PURE__ */ y.createElement("button", {
|
||||||
}, /* @__PURE__ */ React.createElement("button", {
|
className: $(F.button, d.started ? "sc-animate-pulse" : ""),
|
||||||
className: cx(styles.button, cycle.started ? "sc-animate-pulse" : ""),
|
onClick: () => g()
|
||||||
onClick: () => togglePlay()
|
}, /* @__PURE__ */ y.createElement(W, {
|
||||||
}, /* @__PURE__ */ React.createElement(Icon, {
|
type: d.started ? "pause" : "play"
|
||||||
type: cycle.started ? "pause" : "play"
|
})), /* @__PURE__ */ y.createElement("button", {
|
||||||
})), /* @__PURE__ */ React.createElement("button", {
|
className: $(k ? F.button : F.buttonDisabled),
|
||||||
className: cx(dirty ? styles.button : styles.buttonDisabled),
|
onClick: () => c()
|
||||||
onClick: () => activateCode()
|
}, /* @__PURE__ */ y.createElement(W, {
|
||||||
}, /* @__PURE__ */ React.createElement(Icon, {
|
|
||||||
type: "refresh"
|
type: "refresh"
|
||||||
}))), error && /* @__PURE__ */ React.createElement("div", {
|
}))), p && /* @__PURE__ */ y.createElement("div", {
|
||||||
className: styles.error
|
className: F.error
|
||||||
}, error.message)), /* @__PURE__ */ React.createElement("div", {
|
}, p.message)), /* @__PURE__ */ y.createElement("div", {
|
||||||
className: styles.body
|
className: F.body
|
||||||
}, show && /* @__PURE__ */ React.createElement(CodeMirror, {
|
}, L && /* @__PURE__ */ y.createElement(de, {
|
||||||
value: code,
|
value: i,
|
||||||
onChange: setCode,
|
onChange: a,
|
||||||
onViewChanged: setView
|
onViewChanged: h
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
function Te(e, o, r = 0.05, n = 0.1, s = 0.1) {
|
||||||
/*
|
let i = 0, a = 0, t = 10 ** 4, l = 0.01;
|
||||||
useWebMidi.js - <short description TODO>
|
const c = (h) => r = h(r);
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/useWebMidi.js>
|
s = s || n / 2;
|
||||||
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/>.
|
const f = () => {
|
||||||
*/
|
const h = e(), D = h + n + s;
|
||||||
|
for (a === 0 && (a = h + l); a < D; )
|
||||||
function useWebMidi(props) {
|
a = Math.round(a * t) / t, a >= h && o(a, r, i), a < h && console.log("TOO LATE", a), a += r, i++;
|
||||||
const { ready, connected, disconnected } = props;
|
};
|
||||||
const [loading, setLoading] = useState(true);
|
let p;
|
||||||
const [outputs, setOutputs] = useState(WebMidi?.outputs || []);
|
const d = () => {
|
||||||
useEffect(() => {
|
f(), p = setInterval(f, n * 1e3);
|
||||||
enableWebMidi()
|
}, k = () => clearInterval(p);
|
||||||
.then(() => {
|
return { setDuration: c, start: d, stop: () => {
|
||||||
// Reacting when a new device becomes available
|
i = 0, a = 0, k();
|
||||||
WebMidi.addListener('connected', (e) => {
|
}, pause: () => k(), duration: r, getPhase: () => a };
|
||||||
setOutputs([...WebMidi.outputs]);
|
|
||||||
connected?.(WebMidi, e);
|
|
||||||
});
|
|
||||||
// Reacting when a device becomes unavailable
|
|
||||||
WebMidi.addListener('disconnected', (e) => {
|
|
||||||
setOutputs([...WebMidi.outputs]);
|
|
||||||
disconnected?.(WebMidi, e);
|
|
||||||
});
|
|
||||||
ready?.(WebMidi);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
//throw new Error("Web Midi could not be enabled...");
|
|
||||||
console.warn('Web Midi could not be enabled..');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [ready, connected, disconnected, outputs]);
|
|
||||||
const outputByName = (name) => WebMidi.getOutputByName(name);
|
|
||||||
return { loading, outputs, outputByName };
|
|
||||||
}
|
}
|
||||||
|
class _e {
|
||||||
export { CodeMirror, MiniRepl, cx, flash, useCycle, useHighlighting, usePostMessage, useRepl, useWebMidi };
|
worker;
|
||||||
|
pattern;
|
||||||
|
started = !1;
|
||||||
|
cps = 1;
|
||||||
|
getTime;
|
||||||
|
phase = 0;
|
||||||
|
constructor({ interval: o, onTrigger: r, onError: n, getTime: s, latency: i = 0.1 }) {
|
||||||
|
this.getTime = s;
|
||||||
|
const a = (t) => Math.round(t * 1e3) / 1e3;
|
||||||
|
this.clock = Te(
|
||||||
|
s,
|
||||||
|
(t, l, c) => {
|
||||||
|
c === 0 && (this.origin = t);
|
||||||
|
const f = a(t - this.origin);
|
||||||
|
this.phase = f - i;
|
||||||
|
const p = a(f + l), d = s();
|
||||||
|
try {
|
||||||
|
this.pattern.queryArc(f, p).forEach((g) => {
|
||||||
|
if (g.part.begin.equals(g.whole.begin)) {
|
||||||
|
const C = g.whole.begin + this.origin - d + i, v = g.duration * 1;
|
||||||
|
r?.(g, C, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (k) {
|
||||||
|
console.warn("scheduler error", k), n?.(k);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
o
|
||||||
|
);
|
||||||
|
}
|
||||||
|
getPhase() {
|
||||||
|
return this.phase;
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
if (!this.pattern)
|
||||||
|
throw new Error("Scheduler: no pattern set! call .setPattern first.");
|
||||||
|
this.clock.start(), this.started = !0;
|
||||||
|
}
|
||||||
|
pause() {
|
||||||
|
this.clock.stop(), delete this.origin, this.started = !1;
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
delete this.origin, this.clock.stop(), this.started = !1;
|
||||||
|
}
|
||||||
|
setPattern(o) {
|
||||||
|
this.pattern = o;
|
||||||
|
}
|
||||||
|
setCps(o = 1) {
|
||||||
|
this.cps = o;
|
||||||
|
}
|
||||||
|
log(o, r, n) {
|
||||||
|
const s = n.filter((i) => i.hasOnset());
|
||||||
|
console.log(`${o.toFixed(4)} - ${r.toFixed(4)} ${Array(s.length).fill("I").join("")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function Ve({ defaultOutput: e, interval: o, getTime: r, code: n, evalOnMount: s = !1 }) {
|
||||||
|
const [i, a] = w(), [t, l] = w(), [c, f] = w(n), [p, d] = w(), k = n !== c, g = R(
|
||||||
|
() => new _e({ interval: o, onTrigger: e, onError: a, getTime: r }),
|
||||||
|
[e, o]
|
||||||
|
), C = T(async () => {
|
||||||
|
if (!n) {
|
||||||
|
console.log("no code..");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { pattern: h } = await G(n);
|
||||||
|
f(n), g?.setPattern(h), d(h), l();
|
||||||
|
} catch (h) {
|
||||||
|
l(h), console.warn("eval error", h);
|
||||||
|
}
|
||||||
|
}, [n, g]), v = I();
|
||||||
|
return q(() => {
|
||||||
|
!v.current && s && (v.current = !0, C());
|
||||||
|
}, [C, s]), { schedulerError: i, scheduler: g, evalError: t, evaluate: C, activeCode: c, isDirty: k, pattern: p };
|
||||||
|
}
|
||||||
|
const $e = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
|
||||||
|
function We(e) {
|
||||||
|
const { ready: o, connected: r, disconnected: n } = e, [s, i] = w(!0), [a, t] = w(N?.outputs || []);
|
||||||
|
return q(() => {
|
||||||
|
se().then(() => {
|
||||||
|
N.addListener("connected", (c) => {
|
||||||
|
t([...N.outputs]), r?.(N, c);
|
||||||
|
}), N.addListener("disconnected", (c) => {
|
||||||
|
t([...N.outputs]), n?.(N, c);
|
||||||
|
}), o?.(N), i(!1);
|
||||||
|
}).catch((c) => {
|
||||||
|
if (c) {
|
||||||
|
console.error(c), console.warn("Web Midi could not be enabled..");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [o, r, n, a]), { loading: s, outputs: a, outputByName: (c) => N.getOutputByName(c) };
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
de as CodeMirror,
|
||||||
|
Be as MiniRepl,
|
||||||
|
$ as cx,
|
||||||
|
ie as flash,
|
||||||
|
fe as useCycle,
|
||||||
|
ye as useHighlighting,
|
||||||
|
$e as useKeydown,
|
||||||
|
ge as usePostMessage,
|
||||||
|
pe as useRepl,
|
||||||
|
Ve as useStrudel,
|
||||||
|
We as useWebMidi
|
||||||
|
};
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:16px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto}
|
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:16px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
!dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:16px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.fixed{position:fixed}.absolute{position:absolute}.bottom-0{bottom:0px}.z-\[12\]{z-index:12}.flex{display:flex}.w-full{width:100%}.justify-center{justify-content:center}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}.px-2{padding-left:.5rem;padding-right:.5rem}body{background:#123}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
import{f as s,i as t,h as o,j as d,q as f,l as p,k as u,p as i,o as l,n as r,w as g}from"./index.ec9f9930.js";export{s as getAudioContext,t as getCachedBuffer,o as getDestination,d as getLoadedBuffer,f as getLoadedSamples,p as loadBuffer,u as loadGithubSamples,i as panic,l as resetLoadedSamples,r as samples,g as webaudioOutput};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
import{P as w}from"./index.ec9f9930.js";var i,f=!1;async function b(a=38400){if(!f){if(f=!0,i)return i;if("serial"in navigator){const r=await navigator.serial.requestPort();await r.open({baudRate:a});const o=new TextEncoderStream;o.readable.pipeTo(r.writable);const s=o.writable.getWriter();i=function(e){s.write(e)}}else throw"Webserial is not available in this browser."}}const g=.1;w.prototype.serial=function(...a){return this._withHap(r=>{i||b(...a);const o=(s,e,u)=>{var t="";if(typeof e.value=="object")if("action"in e.value){t+=e.value.action+"(";var c=!0;for(const[n,l]of Object.entries(e.value))n!=="action"&&(c?c=!1:t+=",",t+=`${n}:${l}`);t+=")"}else for(const[n,l]of Object.entries(e.value))t+=`${n}:${l};`;else t=e.value;const v=(s-u+g)*1e3;window.setTimeout(i,v,t)};return r.setContext({...r.context,onTrigger:o})})};export{b as getWriter};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Strudel Nano REPL</title>
|
||||||
|
<script type="module" crossorigin src="./assets/index.ec9f9930.js"></script>
|
||||||
|
<link rel="stylesheet" href="./assets/index.75f8960b.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Strudel Nano REPL</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+3520
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "nano-repl",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.0.17",
|
||||||
|
"@types/react-dom": "^18.0.6",
|
||||||
|
"@vitejs/plugin-react": "^2.0.1",
|
||||||
|
"autoprefixer": "^10.4.8",
|
||||||
|
"postcss": "^8.4.16",
|
||||||
|
"tailwindcss": "^3.1.8",
|
||||||
|
"vite": "^3.0.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { evalScope, controls } from '@strudel.cycles/core';
|
||||||
|
import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
|
||||||
|
import useKeydown from '../../../src/hooks/useKeydown.mjs';
|
||||||
|
import useStrudel from '../../../src/hooks/useStrudel';
|
||||||
|
import useHighlighting from '../../../src/hooks/useHighlighting';
|
||||||
|
import './style.css';
|
||||||
|
// import { prebake } from '../../../../../repl/src/prebake.mjs';
|
||||||
|
|
||||||
|
// TODO: only import stuff when play is pressed?
|
||||||
|
evalScope(
|
||||||
|
controls,
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
// import('@strudel.cycles/tone'),
|
||||||
|
// import('@strudel.cycles/midi'), // TODO: find out why midi loads tone.js
|
||||||
|
import('@strudel.cycles/tonal'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
import('@strudel.cycles/xen'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
import('@strudel.cycles/osc'),
|
||||||
|
import('@strudel.cycles/webdirt'),
|
||||||
|
import('@strudel.cycles/serial'),
|
||||||
|
import('@strudel.cycles/soundfonts'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultTune = `samples({
|
||||||
|
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||||
|
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||||
|
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||||
|
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||||
|
stack(
|
||||||
|
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
|
||||||
|
.speed(perlin.range(.7,.9)) // random sample speed variation
|
||||||
|
//.hush()
|
||||||
|
,"<a1 b1*2 a1(3,8) e2>" // bassline
|
||||||
|
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||||
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
|
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||||
|
.n() // wrap in "n"
|
||||||
|
.decay(.15).sustain(0) // make each note of equal length
|
||||||
|
.s('sawtooth') // waveform
|
||||||
|
.gain(.4) // turn down
|
||||||
|
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
|
||||||
|
//.hush()
|
||||||
|
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
|
||||||
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
|
.n() // wrap in "n"
|
||||||
|
.s('square') // waveform
|
||||||
|
.gain(.16) // turn down
|
||||||
|
.cutoff(500) // fixed cutoff
|
||||||
|
.attack(1) // slowly fade in
|
||||||
|
//.hush()
|
||||||
|
,"a4 c5 <e6 a6>".struct("x(5,8)")
|
||||||
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
|
.n() // wrap in "n"
|
||||||
|
.decay(.1).sustain(0) // make notes short
|
||||||
|
.s('triangle') // waveform
|
||||||
|
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
|
||||||
|
.echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes
|
||||||
|
//.hush()
|
||||||
|
)
|
||||||
|
.fast(2/3)`;
|
||||||
|
|
||||||
|
// await prebake();
|
||||||
|
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
const getTime = () => ctx.currentTime;
|
||||||
|
function App() {
|
||||||
|
const [code, setCode] = useState(defaultTune);
|
||||||
|
const [view, setView] = useState();
|
||||||
|
// const [code, setCode] = useState(`"c3".note().slow(2)`);
|
||||||
|
const { scheduler, evaluate, schedulerError, evalError, isDirty, activeCode, pattern } = useStrudel({
|
||||||
|
code,
|
||||||
|
defaultOutput: webaudioOutput,
|
||||||
|
getTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
useHighlighting({
|
||||||
|
view,
|
||||||
|
pattern,
|
||||||
|
active: !activeCode?.includes('strudel disable-highlighting'),
|
||||||
|
getTime: () => scheduler.phase,
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = evalError || schedulerError;
|
||||||
|
useKeydown(
|
||||||
|
useCallback(
|
||||||
|
async (e) => {
|
||||||
|
if (e.ctrlKey || e.altKey) {
|
||||||
|
if (e.code === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
flash(view);
|
||||||
|
await evaluate(code);
|
||||||
|
if (e.shiftKey) {
|
||||||
|
panic();
|
||||||
|
scheduler.stop();
|
||||||
|
scheduler.start();
|
||||||
|
}
|
||||||
|
if (!scheduler.started) {
|
||||||
|
scheduler.start();
|
||||||
|
}
|
||||||
|
} else if (e.code === 'Period') {
|
||||||
|
scheduler.pause();
|
||||||
|
panic();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[scheduler, evaluate, view],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* <textarea value={code} onChange={(e) => setCode(e.target.value)} cols="64" rows="30" /> */}
|
||||||
|
<nav className="z-[12] w-full flex justify-center absolute bottom-0">
|
||||||
|
<div className="bg-slate-500 space-x-2 px-2 rounded-t-md">
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
await evaluate(code);
|
||||||
|
await getAudioContext().resume();
|
||||||
|
scheduler.start();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
start
|
||||||
|
</button>
|
||||||
|
<button onClick={() => scheduler.stop()}>stop</button>
|
||||||
|
{isDirty && <button onClick={() => evaluate(code)}>eval</button>}
|
||||||
|
</div>
|
||||||
|
{error && <p>error {error.message}</p>}
|
||||||
|
</nav>
|
||||||
|
<CodeMirror value={code} onChange={setCode} onViewChanged={setView} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #123;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
tailwind.config.js - <short description TODO>
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/tailwind.config.js>
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// TODO: find out if leaving out tutorial path works now
|
||||||
|
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()]
|
||||||
|
})
|
||||||
+10
-10
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/react",
|
"name": "@strudel.cycles/react",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "React components for strudel",
|
"description": "React components for strudel",
|
||||||
"main": "dist/index.cjs.js",
|
"main": "dist/index.cjs.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
@@ -37,12 +37,12 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^6.0.2",
|
"@codemirror/lang-javascript": "^6.1.1",
|
||||||
"@strudel.cycles/core": "^0.2.0",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/eval": "^0.2.0",
|
"@strudel.cycles/eval": "^0.3.2",
|
||||||
"@strudel.cycles/tone": "^0.2.0",
|
"@strudel.cycles/tone": "^0.3.2",
|
||||||
"@uiw/codemirror-themes": "^4.11.4",
|
"@uiw/codemirror-themes": "^4.12.4",
|
||||||
"@uiw/react-codemirror": "^4.11.4",
|
"@uiw/react-codemirror": "^4.12.4",
|
||||||
"react-hook-inview": "^4.5.0"
|
"react-hook-inview": "^4.5.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -52,12 +52,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^17.0.2",
|
"@types/react": "^17.0.2",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^17.0.2",
|
||||||
"@vitejs/plugin-react": "^1.3.0",
|
"@vitejs/plugin-react": "^2.2.0",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
"postcss": "^8.4.13",
|
"postcss": "^8.4.18",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"tailwindcss": "^3.0.24",
|
"tailwindcss": "^3.0.24",
|
||||||
"vite": "^2.9.9"
|
"vite": "^3.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MiniRepl } from './components/MiniRepl';
|
import { MiniRepl } from './components/MiniRepl';
|
||||||
import 'tailwindcss/tailwind.css';
|
import 'tailwindcss/tailwind.css';
|
||||||
import { evalScope } from '@strudel.cycles/eval';
|
import { controls, evalScope } from '@strudel.cycles/core';
|
||||||
|
|
||||||
evalScope(
|
evalScope(
|
||||||
|
controls,
|
||||||
import('@strudel.cycles/core'),
|
import('@strudel.cycles/core'),
|
||||||
import('@strudel.cycles/tone'),
|
import('@strudel.cycles/tone'),
|
||||||
import('@strudel.cycles/tonal'),
|
import('@strudel.cycles/tonal'),
|
||||||
@@ -16,7 +17,7 @@ evalScope(
|
|||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<MiniRepl tune={`"c3"`} />
|
<MiniRepl tune={`note("c3")`} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'tailwindcss/tailwind.css';
|
|||||||
import './style.css';
|
import './style.css';
|
||||||
import styles from './MiniRepl.module.css';
|
import styles from './MiniRepl.module.css';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
import { Tone } from '@strudel.cycles/tone';
|
||||||
|
|
||||||
export function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableKeyboard }) {
|
export function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableKeyboard }) {
|
||||||
const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } =
|
const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } =
|
||||||
@@ -30,7 +31,12 @@ export function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableK
|
|||||||
}
|
}
|
||||||
return isVisible || wasVisible.current;
|
return isVisible || wasVisible.current;
|
||||||
}, [isVisible, hideOutsideView]);
|
}, [isVisible, hideOutsideView]);
|
||||||
useHighlighting({ view, pattern, active: cycle.started && !activeCode?.includes('strudel disable-highlighting') });
|
useHighlighting({
|
||||||
|
view,
|
||||||
|
pattern,
|
||||||
|
active: cycle.started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
|
getTime: () => Tone.getTransport().seconds,
|
||||||
|
});
|
||||||
|
|
||||||
// set active pattern on ctrl+enter
|
// set active pattern on ctrl+enter
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { setHighlights } from '../components/CodeMirror6';
|
import { setHighlights } from '../components/CodeMirror6';
|
||||||
import { Tone } from '@strudel.cycles/tone';
|
|
||||||
|
|
||||||
let highlights = []; // actively highlighted events
|
function useHighlighting({ view, pattern, active, getTime }) {
|
||||||
let lastEnd;
|
const highlights = useRef([]);
|
||||||
|
const lastEnd = useRef();
|
||||||
function useHighlighting({ view, pattern, active }) {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (view) {
|
if (view) {
|
||||||
if (pattern && active) {
|
if (pattern && active) {
|
||||||
@@ -13,16 +11,16 @@ function useHighlighting({ view, pattern, active }) {
|
|||||||
|
|
||||||
function updateHighlights() {
|
function updateHighlights() {
|
||||||
try {
|
try {
|
||||||
const audioTime = Tone.getTransport().seconds;
|
const audioTime = getTime();
|
||||||
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
|
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
|
||||||
// see https://github.com/tidalcycles/strudel/issues/108
|
// see https://github.com/tidalcycles/strudel/issues/108
|
||||||
const begin = Math.max(lastEnd || audioTime, audioTime - 1 / 10);
|
const begin = Math.max(lastEnd.current || audioTime, audioTime - 1 / 10);
|
||||||
const span = [begin, audioTime + 1 / 60];
|
const span = [begin, audioTime + 1 / 60];
|
||||||
lastEnd = audioTime + 1 / 60;
|
lastEnd.current = audioTime + 1 / 60;
|
||||||
highlights = highlights.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
||||||
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
||||||
highlights = highlights.concat(haps); // add potential new onsets
|
highlights.current = highlights.current.concat(haps); // add potential new onsets
|
||||||
view.dispatch({ effects: setHighlights.of(highlights) }); // highlight all still active + new active haps
|
view.dispatch({ effects: setHighlights.of(highlights.current) }); // highlight all still active + new active haps
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log('error in updateHighlights', err);
|
// console.log('error in updateHighlights', err);
|
||||||
view.dispatch({ effects: setHighlights.of([]) });
|
view.dispatch({ effects: setHighlights.of([]) });
|
||||||
@@ -34,7 +32,7 @@ function useHighlighting({ view, pattern, active }) {
|
|||||||
cancelAnimationFrame(frame);
|
cancelAnimationFrame(frame);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
highlights = [];
|
highlights.current = [];
|
||||||
view.dispatch({ effects: setHighlights.of([]) });
|
view.dispatch({ effects: setHighlights.of([]) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
|
// set active pattern on ctrl+enter
|
||||||
|
const useKeydown = (callback) =>
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
window.addEventListener('keydown', callback, true);
|
||||||
|
return () => window.removeEventListener('keydown', callback, true);
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
export default useKeydown;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { repl } from '@strudel.cycles/core/repl.mjs';
|
||||||
|
import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
|
|
||||||
|
function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = false }) {
|
||||||
|
// scheduler
|
||||||
|
const [schedulerError, setSchedulerError] = useState();
|
||||||
|
const [evalError, setEvalError] = useState();
|
||||||
|
const [activeCode, setActiveCode] = useState(code);
|
||||||
|
const [pattern, setPattern] = useState();
|
||||||
|
const isDirty = code !== activeCode;
|
||||||
|
const { scheduler, evaluate: _evaluate } = useMemo(
|
||||||
|
() =>
|
||||||
|
repl({
|
||||||
|
interval,
|
||||||
|
defaultOutput,
|
||||||
|
onSchedulerError: setSchedulerError,
|
||||||
|
onEvalError: setEvalError,
|
||||||
|
getTime,
|
||||||
|
transpiler,
|
||||||
|
onEval: ({ pattern: _pattern, code }) => {
|
||||||
|
setActiveCode(code);
|
||||||
|
setPattern(_pattern);
|
||||||
|
setEvalError();
|
||||||
|
},
|
||||||
|
onEvalError: setEvalError,
|
||||||
|
}),
|
||||||
|
[defaultOutput, interval, getTime],
|
||||||
|
);
|
||||||
|
const evaluate = useCallback(() => _evaluate(code), [_evaluate, code]);
|
||||||
|
|
||||||
|
const inited = useRef();
|
||||||
|
useEffect(() => {
|
||||||
|
if (!inited.current && evalOnMount && code) {
|
||||||
|
inited.current = true;
|
||||||
|
evaluate();
|
||||||
|
}
|
||||||
|
}, [evaluate, evalOnMount, code]);
|
||||||
|
|
||||||
|
return { schedulerError, scheduler, evalError, evaluate, activeCode, isDirty, pattern };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useStrudel;
|
||||||
@@ -6,5 +6,7 @@ export { default as useCycle } from './hooks/useCycle';
|
|||||||
export { default as useHighlighting } from './hooks/useHighlighting';
|
export { default as useHighlighting } from './hooks/useHighlighting';
|
||||||
export { default as usePostMessage } from './hooks/usePostMessage';
|
export { default as usePostMessage } from './hooks/usePostMessage';
|
||||||
export { default as useRepl } from './hooks/useRepl';
|
export { default as useRepl } from './hooks/useRepl';
|
||||||
|
export { default as useStrudel } from './hooks/useStrudel';
|
||||||
|
export { default as useKeydown } from './hooks/useKeydown';
|
||||||
export { default as cx } from './cx';
|
export { default as cx } from './cx';
|
||||||
export { useWebMidi } from './hooks/useWebMidi';
|
export { useWebMidi } from './hooks/useWebMidi';
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/soundfonts",
|
"name": "@strudel.cycles/soundfonts",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/soundfonts",
|
"name": "@strudel.cycles/soundfonts",
|
||||||
"version": "0.2.1",
|
"version": "0.3.2",
|
||||||
"description": "Soundsfont support for strudel",
|
"description": "Soundsfont support for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.2.0",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/webaudio": "^0.2.0",
|
"@strudel.cycles/webaudio": "^0.3.2",
|
||||||
"sfumato": "^0.1.2",
|
"sfumato": "^0.1.2",
|
||||||
"soundfont2": "^0.4.0"
|
"soundfont2": "^0.4.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/tonal",
|
"name": "@strudel.cycles/tonal",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/tonal",
|
"name": "@strudel.cycles/tonal",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "Tonal functions for strudel",
|
"description": "Tonal functions for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.2.0",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@tonaljs/tonal": "^4.6.5",
|
"@tonaljs/tonal": "^4.6.5",
|
||||||
"webmidi": "^3.0.15"
|
"webmidi": "^3.0.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/tone",
|
"name": "@strudel.cycles/tone",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/tone",
|
"name": "@strudel.cycles/tone",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "Tone.js API for strudel",
|
"description": "Tone.js API for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -22,8 +22,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.2.0",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@tonejs/piano": "^0.2.1",
|
|
||||||
"chord-voicings": "^0.0.1",
|
"chord-voicings": "^0.0.1",
|
||||||
"tone": "^14.7.77"
|
"tone": "^14.7.77"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ const {
|
|||||||
getDestination,
|
getDestination,
|
||||||
Players,
|
Players,
|
||||||
} = Tone;
|
} = Tone;
|
||||||
import * as tonePiano from '@tonejs/piano';
|
|
||||||
const { Piano } = tonePiano;
|
|
||||||
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
|
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
|
||||||
|
|
||||||
// "balanced" | "interactive" | "playback";
|
// "balanced" | "interactive" | "playback";
|
||||||
@@ -61,10 +59,6 @@ Pattern.prototype.tone = function (instrument) {
|
|||||||
instrument.triggerAttack(note, time);
|
instrument.triggerAttack(note, time);
|
||||||
} else if (instrument instanceof NoiseSynth) {
|
} else if (instrument instanceof NoiseSynth) {
|
||||||
instrument.triggerAttackRelease(hap.duration.valueOf(), time); // noise has no value
|
instrument.triggerAttackRelease(hap.duration.valueOf(), time); // noise has no value
|
||||||
} else if (instrument instanceof Piano) {
|
|
||||||
note = getPlayableNoteValue(hap);
|
|
||||||
instrument.keyDown({ note, time, velocity });
|
|
||||||
instrument.keyUp({ note, time: time + hap.duration.valueOf(), velocity });
|
|
||||||
} else if (instrument instanceof Sampler) {
|
} else if (instrument instanceof Sampler) {
|
||||||
note = getPlayableNoteValue(hap);
|
note = getPlayableNoteValue(hap);
|
||||||
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
|
||||||
@@ -110,11 +104,6 @@ export const players = (options, baseUrl = '') => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const synth = (options) => new Synth(options);
|
export const synth = (options) => new Synth(options);
|
||||||
export const piano = async (options = { velocities: 1 }) => {
|
|
||||||
const p = new Piano(options);
|
|
||||||
await p.load();
|
|
||||||
return p;
|
|
||||||
};
|
|
||||||
|
|
||||||
// effect helpers
|
// effect helpers
|
||||||
export const vol = (v) => new Gain(v);
|
export const vol = (v) => new Gain(v);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# @strudel.cycles/transpiler
|
||||||
|
|
||||||
|
This package contains a JS code transpiler with the following features:
|
||||||
|
|
||||||
|
- add locations of mini notation strings (double quoted or backticked) for highlighting
|
||||||
|
- converts pseudo note variables to note strings
|
||||||
|
- adds return statement to the last expression
|
||||||
|
|
||||||
|
The transpiler is written with [acorn](https://github.com/acornjs/acorn) and aims to replace the `@strudel.cycles/eval` package, which uses [shift-ast](https://www.npmjs.com/package/shift-ast).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i @strudel.cycles/transpiler
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { transpiler } from '@strudel.cycles/core';
|
||||||
|
import { evaluate } from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
transpiler('note("c3 [e3,g3]")', { wrapAsync: false, addReturn: false, simpleLocs: true });
|
||||||
|
/* mini('c3 [e3,g3]').withMiniLocation(7,17) */
|
||||||
|
|
||||||
|
evaluate(note('c3 [e3,g3]'), transpiler); // returns pattern of above code
|
||||||
|
```
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { evaluate as _evaluate } from '@strudel.cycles/core';
|
||||||
|
import { transpiler } from './transpiler.mjs';
|
||||||
|
export * from './transpiler.mjs';
|
||||||
|
|
||||||
|
export const evaluate = (code) => _evaluate(code, transpiler);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel.cycles/transpiler",
|
||||||
|
"version": "0.3.2",
|
||||||
|
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||||
|
"main": "index.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"test": "vitest run"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"tidalcycles",
|
||||||
|
"strudel",
|
||||||
|
"pattern",
|
||||||
|
"livecoding",
|
||||||
|
"algorave"
|
||||||
|
],
|
||||||
|
"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.cycles/core": "^0.3.2",
|
||||||
|
"acorn": "^8.8.1",
|
||||||
|
"escodegen": "^2.0.0",
|
||||||
|
"estree-walker": "^3.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
transpiler.test.mjs - <short description TODO>
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/transpiler/test/transpiler.test.mjs>
|
||||||
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { transpiler } from '../transpiler.mjs';
|
||||||
|
|
||||||
|
const simple = { wrapAsync: false, addReturn: false, simpleLocs: true };
|
||||||
|
|
||||||
|
describe('transpiler', () => {
|
||||||
|
it('wraps double quote string with mini and adds location', () => {
|
||||||
|
expect(transpiler('"c3"', simple)).toEqual("mini('c3').withMiniLocation(0, 4);");
|
||||||
|
expect(transpiler('stack("c3","bd sd")', simple)).toEqual(
|
||||||
|
"stack(mini('c3').withMiniLocation(6, 10), mini('bd sd').withMiniLocation(11, 18));",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('wraps backtick string with mini and adds location', () => {
|
||||||
|
expect(transpiler('`c3`', simple)).toEqual("mini('c3').withMiniLocation(0, 4);");
|
||||||
|
});
|
||||||
|
it('replaces note variables with note strings', () => {
|
||||||
|
expect(transpiler('seq(c3, d3)', simple)).toEqual("seq('c3', 'd3');");
|
||||||
|
});
|
||||||
|
it('keeps tagged template literal as is', () => {
|
||||||
|
expect(transpiler('xxx`c3`', simple)).toEqual('xxx`c3`;');
|
||||||
|
});
|
||||||
|
it('supports top level await', () => {
|
||||||
|
expect(transpiler("await samples('xxx');", simple)).toEqual("await samples('xxx');");
|
||||||
|
});
|
||||||
|
/* it('parses dynamic imports', () => {
|
||||||
|
expect(
|
||||||
|
transpiler("const { default: foo } = await import('https://bar.com/foo.js');", {
|
||||||
|
wrapAsync: false,
|
||||||
|
addReturn: false,
|
||||||
|
}),
|
||||||
|
).toEqual("const {default: foo} = await import('https://bar.com/foo.js');");
|
||||||
|
}); */
|
||||||
|
});
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import escodegen from 'escodegen';
|
||||||
|
import { parse } from 'acorn';
|
||||||
|
import { walk } from 'estree-walker';
|
||||||
|
import { isNote } from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
export function transpiler(input, options = {}) {
|
||||||
|
const { wrapAsync = false, addReturn = true, simpleLocs = false } = options;
|
||||||
|
|
||||||
|
let ast = parse(input, {
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
allowAwaitOutsideFunction: true,
|
||||||
|
locations: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
walk(ast, {
|
||||||
|
enter(node, parent, prop, index) {
|
||||||
|
if (isBackTickString(node, parent)) {
|
||||||
|
const { quasis, start, end } = node;
|
||||||
|
const { raw } = quasis[0].value;
|
||||||
|
this.skip();
|
||||||
|
return this.replace(miniWithLocation(raw, node, simpleLocs));
|
||||||
|
}
|
||||||
|
if (isStringWithDoubleQuotes(node)) {
|
||||||
|
const { value, start, end } = node;
|
||||||
|
this.skip();
|
||||||
|
return this.replace(miniWithLocation(value, node, simpleLocs));
|
||||||
|
}
|
||||||
|
if (node.type === 'Identifier' && isNote(node.name)) {
|
||||||
|
this.skip();
|
||||||
|
return this.replace({ type: 'Literal', value: node.name });
|
||||||
|
}
|
||||||
|
// TODO:
|
||||||
|
},
|
||||||
|
leave(node, parent, prop, index) {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { body } = ast;
|
||||||
|
if (!body?.[body.length - 1]?.expression) {
|
||||||
|
throw new Error('unexpected ast format without body expression');
|
||||||
|
}
|
||||||
|
|
||||||
|
// add return to last statement
|
||||||
|
if (addReturn) {
|
||||||
|
const { expression } = body[body.length - 1];
|
||||||
|
body[body.length - 1] = {
|
||||||
|
type: 'ReturnStatement',
|
||||||
|
argument: expression,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const output = escodegen.generate(ast);
|
||||||
|
if (wrapAsync) {
|
||||||
|
return `(async ()=>{${output}})()`;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStringWithDoubleQuotes(node, locations, code) {
|
||||||
|
const { raw, type } = node;
|
||||||
|
if (type !== 'Literal') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return raw[0] === '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBackTickString(node, parent) {
|
||||||
|
return node.type === 'TemplateLiteral' && parent.type !== 'TaggedTemplateExpression';
|
||||||
|
}
|
||||||
|
|
||||||
|
function miniWithLocation(value, node, simpleLocs) {
|
||||||
|
let locs;
|
||||||
|
const { start: fromOffset, end: toOffset } = node;
|
||||||
|
if (simpleLocs) {
|
||||||
|
locs = [
|
||||||
|
{
|
||||||
|
type: 'Literal',
|
||||||
|
value: fromOffset,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Literal',
|
||||||
|
value: toOffset,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
loc: {
|
||||||
|
start: { line: fromLine, column: fromColumn },
|
||||||
|
end: { line: toLine, column: toColumn },
|
||||||
|
},
|
||||||
|
} = node;
|
||||||
|
locs = [
|
||||||
|
{
|
||||||
|
type: 'ArrayExpression',
|
||||||
|
elements: [fromLine, fromColumn, fromOffset].map((value) => ({
|
||||||
|
type: 'Literal',
|
||||||
|
value,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ArrayExpression',
|
||||||
|
elements: [toLine, toColumn, toOffset].map((value) => ({
|
||||||
|
type: 'Literal',
|
||||||
|
value,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// with location
|
||||||
|
return {
|
||||||
|
type: 'CallExpression',
|
||||||
|
callee: {
|
||||||
|
type: 'MemberExpression',
|
||||||
|
object: {
|
||||||
|
type: 'CallExpression',
|
||||||
|
callee: {
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'mini',
|
||||||
|
},
|
||||||
|
arguments: [{ type: 'Literal', value }],
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
property: {
|
||||||
|
type: 'Identifier',
|
||||||
|
name: 'withMiniLocation',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
arguments: locs,
|
||||||
|
optional: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
clockworker.mjs - <short description TODO>
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/webaudio/clockworker.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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// helpers to create a worker dynamically without needing a server / extra file
|
|
||||||
const stringifyFunction = (func) => '(' + func + ')();';
|
|
||||||
const urlifyFunction = (func) => URL.createObjectURL(new Blob([stringifyFunction(func)], { type: 'text/javascript' }));
|
|
||||||
const createWorker = (func) => new Worker(urlifyFunction(func));
|
|
||||||
|
|
||||||
// this is just a setInterval with a counter, running in a worker
|
|
||||||
export class ClockWorker {
|
|
||||||
worker;
|
|
||||||
interval = 0.1; // query span
|
|
||||||
tick = 0;
|
|
||||||
constructor(callback, interval = this.interval) {
|
|
||||||
this.interval = interval;
|
|
||||||
this.worker = createWorker(() => {
|
|
||||||
// we cannot use closures here!
|
|
||||||
let interval;
|
|
||||||
let timerID = null; // this is clock #1 (the sloppy js clock)
|
|
||||||
const clear = () => {
|
|
||||||
if (timerID) {
|
|
||||||
clearInterval(timerID);
|
|
||||||
timerID = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const start = () => {
|
|
||||||
clear();
|
|
||||||
if (!interval) {
|
|
||||||
throw new Error('no interval set! call worker.postMessage({interval}) before starting.');
|
|
||||||
}
|
|
||||||
postMessage('tick');
|
|
||||||
timerID = setInterval(() => postMessage('tick'), interval * 1000);
|
|
||||||
};
|
|
||||||
self.onmessage = function (e) {
|
|
||||||
if (e.data == 'start') {
|
|
||||||
start();
|
|
||||||
} else if (e.data.interval) {
|
|
||||||
interval = e.data.interval;
|
|
||||||
if (timerID) {
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
} else if (e.data == 'stop') {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.worker.postMessage({ interval });
|
|
||||||
// const round = (n, d) => Math.round(n * d) / d;
|
|
||||||
this.worker.onmessage = (e) => {
|
|
||||||
if (e.data === 'tick') {
|
|
||||||
// callback with query span, using clock #2 (the audio clock)
|
|
||||||
callback(this.tick++, this.interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
start() {
|
|
||||||
// console.log('start...');
|
|
||||||
this.worker.postMessage('start');
|
|
||||||
}
|
|
||||||
stop() {
|
|
||||||
// console.log('stop...');
|
|
||||||
this.worker.postMessage('stop');
|
|
||||||
this.tick = 0;
|
|
||||||
}
|
|
||||||
setInterval(interval) {
|
|
||||||
this.worker.postMessage({ interval });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<div style="position: absolute; top: 0; right: 0; padding: 4px">
|
|
||||||
<button id="start" style="margin-bottom: 4px; font-size: 2em">start</button><br />
|
|
||||||
<button id="stop" style="margin-bottom: 4px; font-size: 2em">stop</button><br />
|
|
||||||
<button id="slower" style="font-size: 2em">-</button>
|
|
||||||
<button id="faster" style="font-size: 2em">+</button>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
style="font-size: 2em; background: #052b49; color: #fff; height: 100%; width: 100%; outline: none; border: 0"
|
|
||||||
id="text"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
Loading...</textarea
|
|
||||||
>
|
|
||||||
<script type="module">
|
|
||||||
document.body.style = 'margin: 0';
|
|
||||||
import * as strudel from '@strudel.cycles/core';
|
|
||||||
import * as util from '@strudel.cycles/core/util.mjs';
|
|
||||||
import '@strudel.cycles/core/euclid.mjs';
|
|
||||||
import { Scheduler, getAudioContext } from '../index.mjs';
|
|
||||||
|
|
||||||
const { cat, State, TimeSpan } = strudel;
|
|
||||||
Object.assign(window, strudel); // add strudel to eval scope
|
|
||||||
|
|
||||||
const scheduler = new Scheduler({
|
|
||||||
audioContext: getAudioContext(),
|
|
||||||
interval: 0.1,
|
|
||||||
onEvent: (e) => {
|
|
||||||
e.context?.createAudioNode?.(e);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let initialCode = `sequence(1,2).mul(55/2) // frequencies
|
|
||||||
.mul(slowcat(1,2))
|
|
||||||
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
|
|
||||||
.fast(3)
|
|
||||||
.freq()
|
|
||||||
.velocity(.5)
|
|
||||||
.s('sawtooth')
|
|
||||||
.cutoff(800)
|
|
||||||
.out()
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const base64 = decodeURIComponent(window.location.href.split('#')[1]);
|
|
||||||
initialCode = atob(base64);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('failed to decode', err);
|
|
||||||
}
|
|
||||||
const input = document.getElementById('text');
|
|
||||||
input.value = initialCode;
|
|
||||||
const evaluate = () => {
|
|
||||||
try {
|
|
||||||
const pattern = eval(input.value);
|
|
||||||
scheduler.setPattern(pattern);
|
|
||||||
window.location.hash = '#' + encodeURIComponent(btoa(input.value)); // update url hash
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
evaluate();
|
|
||||||
input.addEventListener('input', () => evaluate());
|
|
||||||
|
|
||||||
document.getElementById('start').addEventListener('click', () => scheduler.start());
|
|
||||||
document.getElementById('stop').addEventListener('click', () => scheduler.stop());
|
|
||||||
document.getElementById('slower').addEventListener('click', () => scheduler.setCps(scheduler.cps - 0.1));
|
|
||||||
document.getElementById('faster').addEventListener('click', () => scheduler.setCps(scheduler.cps + 0.1));
|
|
||||||
</script>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user