Merge remote-tracking branch 'origin/HEAD' into dirtify-plain-values

This commit is contained in:
Felix Roos
2022-04-16 10:30:00 +02:00
37 changed files with 7869 additions and 1971 deletions
+2 -1
View File
@@ -26,4 +26,5 @@ node_modules/
.DS_Store
repl-parcel
mytunes.ts
doc
doc
.parcel-cache
+3 -3
View File
@@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.0d689283.css",
"main.js": "/static/js/main.194ee673.js",
"main.js": "/static/js/main.fafba4eb.js",
"static/js/787.1c52cb78.chunk.js": "/static/js/787.1c52cb78.chunk.js",
"static/media/logo.svg": "/static/media/logo.ac95051720b3dccfe511e0e02d8e1029.svg",
"index.html": "/index.html",
"main.0d689283.css.map": "/static/css/main.0d689283.css.map",
"main.194ee673.js.map": "/static/js/main.194ee673.js.map",
"main.fafba4eb.js.map": "/static/js/main.fafba4eb.js.map",
"787.1c52cb78.chunk.js.map": "/static/js/787.1c52cb78.chunk.js.map"
},
"entrypoints": [
"static/css/main.0d689283.css",
"static/js/main.194ee673.js"
"static/js/main.fafba4eb.js"
]
}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Strudel REPL"/><title>Strudel REPL</title><script defer="defer" src="/static/js/main.194ee673.js"></script><link href="/static/css/main.0d689283.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Strudel REPL"/><title>Strudel REPL</title><script defer="defer" src="/static/js/main.fafba4eb.js"></script><link href="/static/css/main.0d689283.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
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
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico"><link rel="stylesheet" type="text/css" href="/tutorial/index.757e9f9d.css"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Strudel REPL"><title>Strudel Tutorial</title></head><body> <div id="root"></div> <noscript>You need to enable JavaScript to run this app.</noscript> <script src="/tutorial/index.3b5f65fb.js" defer></script> </body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico"><link rel="stylesheet" type="text/css" href="/tutorial/index.757e9f9d.css"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Strudel REPL"><title>Strudel Tutorial</title></head><body> <div id="root"></div> <noscript>You need to enable JavaScript to run this app.</noscript> <script src="/tutorial/index.816241f5.js" defer></script> </body></html>
+3464 -106
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -9,7 +9,8 @@
"bootstrap": "lerna bootstrap",
"setup": "npm i && npm run bootstrap && cd repl && npm i",
"repl": "cd repl && npm run start",
"osc": "cd packages/osc && npm run server"
"osc": "cd packages/osc && npm run server",
"build": "cd repl && npm run build"
},
"workspaces": [
"packages/*"
+1 -1
View File
@@ -11,7 +11,7 @@ npm i @strudel.cycles/core --save
## Example
```js
import { sequence, State, TimeSpan } from '@strudel.cycles/core';
import { sequence } from '@strudel.cycles/core';
const pattern = sequence('a', ['b', 'c']);
+3 -3
View File
@@ -935,18 +935,18 @@ export function makeComposable(func) {
return func;
}
const patternify2 = (f) => (pata, patb, pat) =>
export const patternify2 = (f) => (pata, patb, pat) =>
pata
.fmap((a) => (b) => f.call(pat, a, b))
.appLeft(patb)
.innerJoin();
const patternify3 = (f) => (pata, patb, patc, pat) =>
export const patternify3 = (f) => (pata, patb, patc, pat) =>
pata
.fmap((a) => (b) => (c) => f.call(pat, a, b, c))
.appLeft(patb)
.appLeft(patc)
.innerJoin();
const patternify4 = (f) => (pata, patb, patc, patd, pat) =>
export const patternify4 = (f) => (pata, patb, patc, patd, pat) =>
pata
.fmap((a) => (b) => (c) => (d) => f.call(pat, a, b, c, d))
.appLeft(patb)
+48 -1
View File
@@ -1,5 +1,5 @@
import { Hap } from './hap.mjs';
import { Pattern, fastcat } from './pattern.mjs';
import { Pattern, fastcat, reify, silence } from './pattern.mjs';
import Fraction from './fraction.mjs';
export function steady(value) {
@@ -28,3 +28,50 @@ export const square2 = square._toBipolar();
export const tri = fastcat(isaw, saw);
export const tri2 = fastcat(isaw2, saw2);
// random signals
const xorwise = (x) => {
const a = (x << 13) ^ x;
const b = (a >> 17) ^ a;
return (b << 5) ^ b;
};
// stretch 300 cycles over the range of [0,2**29 == 536870912) then apply the xorshift algorithm
const _frac = (x) => x - Math.trunc(x);
const timeToIntSeed = (x) => xorwise(Math.trunc(_frac(x / 300) * 536870912));
const intSeedToRand = (x) => (x % 536870912) / 536870912;
const timeToRand = (x) => intSeedToRand(timeToIntSeed(x));
const timeToRandsPrime = (seed, n) => {
const result = [];
for (let i = 0; i < n; ++n) {
result.push(intSeedToRand(seed));
seed = xorwise(seed);
}
return result;
};
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
export const rand2 = signal(timeToRand);
export const rand = rand2.fmap(Math.abs);
export const _brandBy = (p) => rand.fmap((x) => x < p);
export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
export const brand = _brandBy(0.5);
export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
export const chooseWith = (pat, xs) => {
if (xs.length == 0) {
return silence;
}
return pat.range(0, xs.length).fmap((i) => xs[Math.floor(i)]);
};
export const choose = (...xs) => chooseWith(rand, xs);
+33
View File
@@ -0,0 +1,33 @@
import { Pattern, patternify2 } from './index.mjs';
const synth = window?.speechSynthesis;
let allVoices = synth?.getVoices();
// console.log('voices', allVoices);
function speak(words, lang, voice) {
synth.cancel();
const utterance = new SpeechSynthesisUtterance(words);
utterance.lang = lang;
allVoices = synth.getVoices();
const voices = allVoices.filter((v) => v.lang.includes(lang));
if (typeof voice === 'number') {
utterance.voice = voices[voice % voices.length];
} else if (typeof voice === 'string') {
utterance.voice = voices.find((voice) => voice.name === voice);
}
// console.log(utterance.voice?.name, utterance.voice?.lang);
speechSynthesis.speak(utterance);
}
Pattern.prototype._speak = function (lang, voice) {
return this._withEvent((event) => {
const onTrigger = (time, event) => {
speak(event.value, lang, voice);
};
return event.setContext({ ...event.context, onTrigger });
});
};
Pattern.prototype.speak = function (lang, voice) {
return patternify2(Pattern.prototype._speak)(reify(lang), reify(voice), this);
};
-1726
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,5 +1,5 @@
import OSC from './osc.js';
import { Pattern, dirtify, objectify } from '@strudel.cycles/core';
import OSC from 'osc-js';
import { Pattern } from '@strudel.cycles/core';
const comm = new OSC();
comm.open();
+9 -25
View File
@@ -9,24 +9,15 @@
"version": "0.0.1",
"license": "GPL-3.0-or-later",
"dependencies": {
"osc-js": "^2.3.0"
}
},
"node_modules/isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
"peerDependencies": {
"ws": "*"
"osc-js": "^2.3.2"
}
},
"node_modules/osc-js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/osc-js/-/osc-js-2.3.0.tgz",
"integrity": "sha512-P2Oy9tf8Z9lQw8JZeR62HNqbKdxj7Kqbsag+ImiJvyxPDReGMVt5LtZbMh/7Ve/wbYEGODkQdFAaLHFVkIlHPw==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/osc-js/-/osc-js-2.3.2.tgz",
"integrity": "sha512-9i7J4u1hH+glooGMh+ki1ni0JGqKmylT8r0nXKugHbRK63rR+kl4O+5tGW6+/EszjbCju3KV+eXQQzFDdGrmhg==",
"dependencies": {
"isomorphic-ws": "4.0.1",
"ws": "8.5.0"
"ws": "^8.5.0"
}
},
"node_modules/ws": {
@@ -51,19 +42,12 @@
}
},
"dependencies": {
"isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
"requires": {}
},
"osc-js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/osc-js/-/osc-js-2.3.0.tgz",
"integrity": "sha512-P2Oy9tf8Z9lQw8JZeR62HNqbKdxj7Kqbsag+ImiJvyxPDReGMVt5LtZbMh/7Ve/wbYEGODkQdFAaLHFVkIlHPw==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/osc-js/-/osc-js-2.3.2.tgz",
"integrity": "sha512-9i7J4u1hH+glooGMh+ki1ni0JGqKmylT8r0nXKugHbRK63rR+kl4O+5tGW6+/EszjbCju3KV+eXQQzFDdGrmhg==",
"requires": {
"isomorphic-ws": "4.0.1",
"ws": "8.5.0"
"ws": "^8.5.0"
}
},
"ws": {
+1 -1
View File
@@ -27,6 +27,6 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"osc-js": "^2.3.0"
"osc-js": "^2.3.2"
}
}
+29
View File
@@ -0,0 +1,29 @@
# @strudel.cycles/webaudio
This package contains a scheduler + a clockworker and synths based on the Web Audio API.
It's an alternative to `@strudel.cycles/tone`, with better performance, but less features.
## Install
```sh
npm i @strudel.cycles/webaudio --save
```
## Example
```js
import { Scheduler, getAudioContext } from '@strudel.cycles/webaudio';
const scheduler = new Scheduler({
audioContext: getAudioContext(),
interval: 0.1,
onEvent: (e) => e.context?.createAudioNode?.(e),
});
const pattern = sequence([55, 99], 110).osc('sawtooth').out()
scheduler.setPattern(pattern);
scheduler.start()
//scheduler.stop()
```
A more sophisticated example can be found in [examples/repl.html](./examples/repl.html).
You can run it inside this directory with `npm run example`.
+69
View File
@@ -0,0 +1,69 @@
// 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 class is basically the tale of two clocks
class ClockWorker {
worker;
audioContext;
interval = 0.2; // query span
lastEnd = 0;
constructor(audioContext, callback, interval = this.interval) {
this.audioContext = audioContext;
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.');
}
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;
const precision = 100;
this.worker.onmessage = (e) => {
if (e.data === 'tick') {
const begin = this.lastEnd || this.audioContext.currentTime;
const end = this.audioContext.currentTime + this.interval; // DONT reference begin here!
this.lastEnd = end;
// callback with query span, using clock #2 (the audio clock)
callback(begin, end);
}
};
}
start() {
console.log('start...');
this.audioContext.resume();
this.worker.postMessage('start');
}
stop() {
console.log('stop...');
this.worker.postMessage('stop');
}
}
export default ClockWorker;
+61
View File
@@ -0,0 +1,61 @@
<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="font-size: 2em">stop</button>
</div>
<textarea
style="font-size: 2em; background: #e8d565; color: #323230; 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 '@strudel.cycles/core/euclid.mjs';
import { Scheduler, getAudioContext } from '@strudel.cycles/webaudio';
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)
.velocity(.5)
.osc(cat('sawtooth','square').fast(2))
.adsr(0.01,.02,.5,0.1)
.filter('lowshelf',800,25)
.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());
</script>
+3
View File
@@ -0,0 +1,3 @@
export { default as ClockWorker } from './clockworker.mjs';
export { default as Scheduler } from './scheduler.mjs';
export * from './webaudio.mjs';
+3851
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.0.1",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"directories": {
"example": "examples"
},
"scripts": {
"example": "parcel examples/repl.html"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Felix Roos <flix91@gmail.com>",
"license": "GPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"devDependencies": {
"parcel": "^2.4.1"
}
}
+38
View File
@@ -0,0 +1,38 @@
import ClockWorker from './clockworker.mjs';
class Scheduler {
worker;
pattern;
constructor({ audioContext, interval = 0.2, onEvent }) {
this.worker = new ClockWorker(
audioContext,
(begin, end) => {
this.pattern.query(new State(new TimeSpan(begin, end))).forEach((e) => {
if (!e.part.begin.equals(e.whole.begin)) {
return;
}
if (onEvent) {
onEvent?.(e);
} else {
console.warn('unplayable event: no audio node nor onEvent callback', e);
}
});
},
interval,
);
}
start() {
if (!this.pattern) {
throw new Error('Scheduler: no pattern set! call .setPattern first.');
}
this.worker.start();
}
stop() {
this.worker.stop();
}
setPattern(pat) {
this.pattern = pat;
}
}
export default Scheduler;
+74
View File
@@ -0,0 +1,74 @@
import { Pattern } from '@strudel.cycles/core';
let audioContext;
export const getAudioContext = () => {
if (!audioContext) {
audioContext = new AudioContext();
}
return audioContext;
};
const lookahead = 0.2;
const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
const gainNode = getAudioContext().createGain();
gainNode.gain.setValueAtTime(0, begin);
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain start
gainNode.gain.setValueAtTime(sustain * velocity, end); // sustain end
gainNode.gain.linearRampToValueAtTime(0, end + release); // release
// for some reason, using exponential ramping creates little cracklings
return gainNode;
};
Pattern.prototype.withAudioNode = function (createAudioNode) {
return this._withEvent((event) => {
return event.setContext({
...event.context,
createAudioNode: (e) => createAudioNode(e, event.context.createAudioNode?.(event)),
});
});
};
Pattern.prototype._osc = function (type) {
return this.withAudioNode((e) => {
const osc = getAudioContext().createOscillator();
osc.type = type;
osc.frequency.value = e.value; // expects frequency..
osc.start(e.whole.begin.valueOf() + lookahead);
osc.stop(e.whole.end.valueOf() + lookahead); // release?
return osc;
});
};
Pattern.prototype.adsr = function (a = 0.01, d = 0.05, s = 1, r = 0.01) {
return this.withAudioNode((e, node) => {
const velocity = e.context?.velocity || 1;
const envelope = adsr(a, d, s, r, velocity, e.whole.begin.valueOf() + lookahead, e.whole.end.valueOf() + lookahead);
node?.connect(envelope);
return envelope;
});
};
Pattern.prototype.filter = function (type = 'lowshelf', frequency = 1000, gain = 25) {
return this.withAudioNode((e, node) => {
const filter = getAudioContext().createBiquadFilter();
filter.type = type;
filter.frequency.value = frequency;
filter.gain.value = gain;
node?.connect(filter);
return filter;
});
};
Pattern.prototype.out = function () {
const master = getAudioContext().createGain();
master.gain.value = 0.1;
master.connect(getAudioContext().destination);
return this.withAudioNode((e, node) => {
if (!node) {
console.warn('out: no source! call .osc() first');
}
node?.connect(master);
});
};
Pattern.prototype.define('osc', (type, pat) => pat.osc(type), { patternified: true });
+2 -9
View File
@@ -13,11 +13,8 @@ The REPL is deployed at [strudel.tidalcycles.org](https://strudel.tidalcycles.or
```bash
# from project root
npm install
npx lerna bootstrap
cd repl
npm install
npm run start
npm run setup
npm run repl
```
## Build REPL
@@ -27,7 +24,3 @@ cd repl
npm run build # <- builds repl + tutorial to ../docs
npm run static # <- test static build
```
## Dev Notes
~~Always run `npm i --legacy-peer-deps`, otherwise `@tonejs/piano` will break.~~
+16 -5
View File
@@ -26,17 +26,28 @@ import '@strudel.cycles/tonal/tonal.mjs';
import '@strudel.cycles/xen/xen.mjs';
import '@strudel.cycles/xen/tune.mjs';
import '@strudel.cycles/core/euclid.mjs';
import '@strudel.cycles/core/speak.mjs';
import '@strudel.cycles/tone/pianoroll.mjs';
import '@strudel.cycles/tone/draw.mjs';
import '@strudel.cycles/osc/osc.mjs';
import controls from '@strudel.cycles/core/controls.mjs';
extend(Tone, strudel, strudel.Pattern.prototype.bootstrap(), controls, toneHelpers, voicingHelpers, drawHelpers, uiHelpers, {
gist,
euclid,
mini,
extend(
Tone,
});
strudel,
strudel.Pattern.prototype.bootstrap(),
controls,
toneHelpers,
voicingHelpers,
drawHelpers,
uiHelpers,
{
gist,
euclid,
mini,
Tone,
},
);
// eval stuff end
const codeParam = window.location.href.split('#')[1];
+39
View File
@@ -703,3 +703,42 @@ stack(
>\`
.legato(.5)
).fast(2) //.tone((await piano()).chain(out()))`;
export const speakerman = `backgroundImage('https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.ytimg.com%2Fvi%2FXR0rKqW3VwY%2Fmaxresdefault.jpg&f=1&nofb=1',
{ className:'darken', style:'background-size:cover'})
stack(
"[g3,bb3,d4] [f3,a3,c4] [c3,e3,g3]@2".slow(2).late(.1),
slowcat(
'Baker man',
'is baking bread',
'Baker man',
'is baking bread',
'Sagabona',
'kunjani wena',
'Sagabona',
'kunjani wena',
'The night train, is coming',
'got to keep on running',
'The night train, is coming',
'got to keep on running',
).speak("en zu en".slow(12), "<0 2 3 4 5 6>".slow(2)),
).slow(4)`;
export const randomBells = `const delay = new FeedbackDelay(1/3, .8).chain(vol(.2), out());
let bell = await sampler({
C6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3'
})
const bass = await sampler({
d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3'
});
bell = bell.chain(vol(0.6).connect(delay),out());
"0".euclidLegato(3,8)
.add(rand.range(0,12))
.echo(3, 1/16, (x,n)=>x.add((n+1)*2).velocity(1/(n+1)))
.velocity(rand.range(.5,1))
.legato(rand.range(.4,3))
.scale(slowcat('D minor pentatonic')).tone(bell)
.stack("<D2 A2 G2 F2>".euclidLegato(6,8,1).tone(bass.toDestination()))
.slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
+1 -1
View File
@@ -6,7 +6,7 @@ import cx from '../cx';
// eval stuff start
import { extend } from '@strudel.cycles/eval';
import * as strudel from '@strudel.cycles/core/strudel.mjs';
import * as strudel from '@strudel.cycles/core';
import gist from '@strudel.cycles/core/gist.js';
import { mini } from '@strudel.cycles/mini/mini.mjs';
import { Tone } from '@strudel.cycles/tone';
+2 -2
View File
@@ -195,14 +195,14 @@ Internally, the mini notation will expand to use the actual functional JavaScrip
Notes are automatically available as variables:
<MiniRepl tune={`e4`} />
<MiniRepl tune={`sequence(d4, fs4, a4)`} />
An important difference to the mini notation:
For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name.
The above is the same as:
<MiniRepl tune={`"e4"`} />
<MiniRepl tune={`sequence('d4', 'f#4', 'a4')`} />
Using strings, you can also use "#".
+1 -1
View File
@@ -39,7 +39,7 @@ function useCycle(props) {
// schedule events for next cycle
events
?.filter((event) => event.part.begin.equals(event.whole.begin))
?.filter((event) => event.part.begin.equals(event.whole?.begin))
.forEach((event) => {
Tone.getTransport().schedule((time) => {
onEvent(time, event, Tone.getContext().currentTime);