mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-19 01:28:28 +00:00
started refac repl to new scheduler + transpiler
This commit is contained in:
parent
c99d957bc8
commit
14c2da4fa2
32
package-lock.json
generated
32
package-lock.json
generated
@ -12495,10 +12495,10 @@
|
|||||||
},
|
},
|
||||||
"packages/midi": {
|
"packages/midi": {
|
||||||
"name": "@strudel.cycles/midi",
|
"name": "@strudel.cycles/midi",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/tone": "^0.3.2",
|
"@strudel.cycles/tone": "^0.3.3",
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
"webmidi": "^3.0.21"
|
"webmidi": "^3.0.21"
|
||||||
}
|
}
|
||||||
@ -12519,12 +12519,12 @@
|
|||||||
},
|
},
|
||||||
"packages/mini": {
|
"packages/mini": {
|
||||||
"name": "@strudel.cycles/mini",
|
"name": "@strudel.cycles/mini",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/eval": "^0.3.2",
|
"@strudel.cycles/eval": "^0.3.2",
|
||||||
"@strudel.cycles/tone": "^0.3.2"
|
"@strudel.cycles/tone": "^0.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"peggy": "^2.0.1"
|
"peggy": "^2.0.1"
|
||||||
@ -12540,13 +12540,13 @@
|
|||||||
},
|
},
|
||||||
"packages/react": {
|
"packages/react": {
|
||||||
"name": "@strudel.cycles/react",
|
"name": "@strudel.cycles/react",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^6.1.1",
|
"@codemirror/lang-javascript": "^6.1.1",
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/eval": "^0.3.2",
|
"@strudel.cycles/eval": "^0.3.2",
|
||||||
"@strudel.cycles/tone": "^0.3.2",
|
"@strudel.cycles/tone": "^0.3.3",
|
||||||
"@uiw/codemirror-themes": "^4.12.4",
|
"@uiw/codemirror-themes": "^4.12.4",
|
||||||
"@uiw/react-codemirror": "^4.12.4",
|
"@uiw/react-codemirror": "^4.12.4",
|
||||||
"react-hook-inview": "^4.5.0"
|
"react-hook-inview": "^4.5.0"
|
||||||
@ -12621,11 +12621,11 @@
|
|||||||
},
|
},
|
||||||
"packages/soundfonts": {
|
"packages/soundfonts": {
|
||||||
"name": "@strudel.cycles/soundfonts",
|
"name": "@strudel.cycles/soundfonts",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/webaudio": "^0.3.2",
|
"@strudel.cycles/webaudio": "^0.3.3",
|
||||||
"sfumato": "^0.1.2",
|
"sfumato": "^0.1.2",
|
||||||
"soundfont2": "^0.4.0"
|
"soundfont2": "^0.4.0"
|
||||||
},
|
},
|
||||||
@ -12653,7 +12653,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tonal": {
|
"packages/tonal": {
|
||||||
"name": "@strudel.cycles/tonal",
|
"name": "@strudel.cycles/tonal",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
@ -12678,7 +12678,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tone": {
|
"packages/tone": {
|
||||||
"name": "@strudel.cycles/tone",
|
"name": "@strudel.cycles/tone",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
@ -12709,7 +12709,7 @@
|
|||||||
},
|
},
|
||||||
"packages/webaudio": {
|
"packages/webaudio": {
|
||||||
"name": "@strudel.cycles/webaudio",
|
"name": "@strudel.cycles/webaudio",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.3.2"
|
"@strudel.cycles/core": "^0.3.2"
|
||||||
@ -14427,7 +14427,7 @@
|
|||||||
"@strudel.cycles/midi": {
|
"@strudel.cycles/midi": {
|
||||||
"version": "file:packages/midi",
|
"version": "file:packages/midi",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@strudel.cycles/tone": "^0.3.2",
|
"@strudel.cycles/tone": "^0.3.3",
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
"webmidi": "^3.0.21"
|
"webmidi": "^3.0.21"
|
||||||
},
|
},
|
||||||
@ -14448,7 +14448,7 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/eval": "^0.3.2",
|
"@strudel.cycles/eval": "^0.3.2",
|
||||||
"@strudel.cycles/tone": "^0.3.2",
|
"@strudel.cycles/tone": "^0.3.3",
|
||||||
"peggy": "^2.0.1"
|
"peggy": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -14464,7 +14464,7 @@
|
|||||||
"@codemirror/lang-javascript": "^6.1.1",
|
"@codemirror/lang-javascript": "^6.1.1",
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/eval": "^0.3.2",
|
"@strudel.cycles/eval": "^0.3.2",
|
||||||
"@strudel.cycles/tone": "^0.3.2",
|
"@strudel.cycles/tone": "^0.3.3",
|
||||||
"@types/react": "^17.0.2",
|
"@types/react": "^17.0.2",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^17.0.2",
|
||||||
"@uiw/codemirror-themes": "^4.12.4",
|
"@uiw/codemirror-themes": "^4.12.4",
|
||||||
@ -14520,7 +14520,7 @@
|
|||||||
"version": "file:packages/soundfonts",
|
"version": "file:packages/soundfonts",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/webaudio": "^0.3.2",
|
"@strudel.cycles/webaudio": "^0.3.3",
|
||||||
"node-fetch": "^3.2.6",
|
"node-fetch": "^3.2.6",
|
||||||
"sfumato": "^0.1.2",
|
"sfumato": "^0.1.2",
|
||||||
"soundfont2": "^0.4.0"
|
"soundfont2": "^0.4.0"
|
||||||
@ -20344,7 +20344,7 @@
|
|||||||
"@codemirror/lang-javascript": "^6.1.1",
|
"@codemirror/lang-javascript": "^6.1.1",
|
||||||
"@strudel.cycles/core": "^0.3.2",
|
"@strudel.cycles/core": "^0.3.2",
|
||||||
"@strudel.cycles/eval": "^0.3.2",
|
"@strudel.cycles/eval": "^0.3.2",
|
||||||
"@strudel.cycles/tone": "^0.3.2",
|
"@strudel.cycles/tone": "^0.3.3",
|
||||||
"@types/react": "^17.0.2",
|
"@types/react": "^17.0.2",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^17.0.2",
|
||||||
"@uiw/codemirror-themes": "^4.12.4",
|
"@uiw/codemirror-themes": "^4.12.4",
|
||||||
|
|||||||
@ -13,8 +13,9 @@ export class Cyclist {
|
|||||||
cps = 1; // TODO
|
cps = 1; // TODO
|
||||||
getTime;
|
getTime;
|
||||||
phase = 0;
|
phase = 0;
|
||||||
constructor({ interval, onTrigger, onError, getTime, latency = 0.1 }) {
|
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
||||||
this.getTime = getTime;
|
this.getTime = getTime;
|
||||||
|
this.onToggle = onToggle;
|
||||||
const round = (x) => Math.round(x * 1000) / 1000;
|
const round = (x) => Math.round(x * 1000) / 1000;
|
||||||
this.clock = createClock(
|
this.clock = createClock(
|
||||||
getTime,
|
getTime,
|
||||||
@ -28,9 +29,7 @@ export class Cyclist {
|
|||||||
const time = getTime();
|
const time = getTime();
|
||||||
try {
|
try {
|
||||||
const haps = this.pattern.queryArc(begin, end); // get Haps
|
const haps = this.pattern.queryArc(begin, end); // get Haps
|
||||||
// console.log('haps', haps.map((hap) => hap.value.n).join(' '));
|
|
||||||
haps.forEach((hap) => {
|
haps.forEach((hap) => {
|
||||||
// console.log('hap', hap.value.n, hap.part.begin);
|
|
||||||
if (hap.part.begin.equals(hap.whole.begin)) {
|
if (hap.part.begin.equals(hap.whole.begin)) {
|
||||||
const deadline = hap.whole.begin + this.origin - time + latency;
|
const deadline = hap.whole.begin + this.origin - time + latency;
|
||||||
const duration = hap.duration * 1;
|
const duration = hap.duration * 1;
|
||||||
@ -48,22 +47,26 @@ export class Cyclist {
|
|||||||
getPhase() {
|
getPhase() {
|
||||||
return this.phase;
|
return this.phase;
|
||||||
}
|
}
|
||||||
|
setStarted(v) {
|
||||||
|
this.started = v;
|
||||||
|
this.onToggle?.(v);
|
||||||
|
}
|
||||||
start() {
|
start() {
|
||||||
if (!this.pattern) {
|
if (!this.pattern) {
|
||||||
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
||||||
}
|
}
|
||||||
this.clock.start();
|
this.clock.start();
|
||||||
this.started = true;
|
this.setStarted(true);
|
||||||
}
|
}
|
||||||
pause() {
|
pause() {
|
||||||
this.clock.stop();
|
this.clock.stop();
|
||||||
delete this.origin;
|
// delete this.origin;
|
||||||
this.started = false;
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
delete this.origin;
|
delete this.origin;
|
||||||
this.clock.stop();
|
this.clock.stop();
|
||||||
this.started = false;
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
setPattern(pat, autostart = false) {
|
setPattern(pat, autostart = false) {
|
||||||
this.pattern = pat;
|
this.pattern = pat;
|
||||||
|
|||||||
@ -1,8 +1,17 @@
|
|||||||
import { Cyclist } from './cyclist.mjs';
|
import { Cyclist } from './cyclist.mjs';
|
||||||
import { evaluate as _evaluate } from './evaluate.mjs';
|
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||||
|
|
||||||
export function repl({ interval, defaultOutput, onSchedulerError, onEvalError, onEval, getTime, transpiler }) {
|
export function repl({
|
||||||
const scheduler = new Cyclist({ interval, onTrigger: defaultOutput, onError: onSchedulerError, getTime });
|
interval,
|
||||||
|
defaultOutput,
|
||||||
|
onSchedulerError,
|
||||||
|
onEvalError,
|
||||||
|
onEval,
|
||||||
|
getTime,
|
||||||
|
transpiler,
|
||||||
|
onToggle,
|
||||||
|
}) {
|
||||||
|
const scheduler = new Cyclist({ interval, onTrigger: defaultOutput, onError: onSchedulerError, getTime, onToggle });
|
||||||
const evaluate = async (code) => {
|
const evaluate = async (code) => {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw new Error('no code to evaluate');
|
throw new Error('no code to evaluate');
|
||||||
|
|||||||
@ -31,10 +31,11 @@ function createClock(
|
|||||||
};
|
};
|
||||||
let intervalID;
|
let intervalID;
|
||||||
const start = () => {
|
const start = () => {
|
||||||
|
clear(); // just in case start was called more than once
|
||||||
onTick();
|
onTick();
|
||||||
intervalID = setInterval(onTick, interval * 1000);
|
intervalID = setInterval(onTick, interval * 1000);
|
||||||
};
|
};
|
||||||
const clear = () => clearInterval(intervalID);
|
const clear = () => intervalID !== undefined && clearInterval(intervalID);
|
||||||
const pause = () => clear();
|
const pause = () => clear();
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
tick = 0;
|
tick = 0;
|
||||||
|
|||||||
@ -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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isNote } from 'tone';
|
|
||||||
import * as _WebMidi from 'webmidi';
|
import * as _WebMidi from 'webmidi';
|
||||||
import { Pattern, isPattern } from '@strudel.cycles/core';
|
import { Pattern, isPattern, isNote } from '@strudel.cycles/core';
|
||||||
import { Tone } from '@strudel.cycles/tone';
|
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// console.log('midi', value, output);
|
// console.log('midi', value, output);
|
||||||
const timingOffset = WebMidi.time - Tone.getContext().currentTime * 1000;
|
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
|
||||||
time = time * 1000 + timingOffset;
|
time = time * 1000 + timingOffset;
|
||||||
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
|
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
|
||||||
// await enableWebMidi()
|
// await enableWebMidi()
|
||||||
|
|||||||
26
packages/react/dist/index.cjs.js
vendored
26
packages/react/dist/index.cjs.js
vendored
File diff suppressed because one or more lines are too long
5880
packages/react/dist/index.es.js
vendored
5880
packages/react/dist/index.es.js
vendored
File diff suppressed because one or more lines are too long
@ -17,6 +17,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"watch": "vite build --watch",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { controls, evalScope } from '@strudel.cycles/core';
|
|||||||
evalScope(
|
evalScope(
|
||||||
controls,
|
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'),
|
||||||
import('@strudel.cycles/mini'),
|
import('@strudel.cycles/mini'),
|
||||||
import('@strudel.cycles/midi'),
|
import('@strudel.cycles/midi'),
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useMemo, useRef, useEffect, useLayoutEffect } from 'react';
|
import React, { useState, useMemo, useRef, useEffect, useLayoutEffect } from 'react';
|
||||||
import { useInView } from 'react-hook-inview';
|
import { useInView } from 'react-hook-inview';
|
||||||
import useRepl from '../hooks/useRepl.mjs';
|
|
||||||
import cx from '../cx';
|
import cx from '../cx';
|
||||||
import useHighlighting from '../hooks/useHighlighting.mjs';
|
import useHighlighting from '../hooks/useHighlighting.mjs';
|
||||||
import CodeMirror6, { flash } from './CodeMirror6';
|
import CodeMirror6, { flash } from './CodeMirror6';
|
||||||
@ -8,10 +7,12 @@ 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';
|
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||||
|
// 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 } =
|
return <p>TODO</p>;
|
||||||
|
/* const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } =
|
||||||
useRepl({
|
useRepl({
|
||||||
tune,
|
tune,
|
||||||
autolink: false,
|
autolink: false,
|
||||||
@ -35,7 +36,7 @@ export function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableK
|
|||||||
view,
|
view,
|
||||||
pattern,
|
pattern,
|
||||||
active: cycle.started && !activeCode?.includes('strudel disable-highlighting'),
|
active: cycle.started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => Tone.getTransport().seconds,
|
getTime: () => getAudioContext().seconds,
|
||||||
});
|
});
|
||||||
|
|
||||||
// set active pattern on ctrl+enter
|
// set active pattern on ctrl+enter
|
||||||
@ -75,5 +76,5 @@ export function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableK
|
|||||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
|
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
); */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
useCycle.mjs - <short description TODO>
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/useCycle.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 { useEffect, useState } from 'react';
|
|
||||||
import { Tone } from '@strudel.cycles/tone';
|
|
||||||
import { State, TimeSpan } from '@strudel.cycles/core';
|
|
||||||
|
|
||||||
/* export declare interface UseCycleProps {
|
|
||||||
onEvent: ToneEventCallback<any>;
|
|
||||||
onQuery?: (state: State) => Hap[];
|
|
||||||
onSchedule?: (events: Hap[], cycle: number) => void;
|
|
||||||
onDraw?: ToneEventCallback<any>;
|
|
||||||
ready?: boolean; // if false, query will not be called on change props
|
|
||||||
} */
|
|
||||||
|
|
||||||
// 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());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
ready && query();
|
|
||||||
}, [onEvent, onSchedule, onQuery, onDraw, ready]);
|
|
||||||
|
|
||||||
const start = async () => {
|
|
||||||
setStarted(true);
|
|
||||||
await Tone.start();
|
|
||||||
Tone.getTransport().start('+0.1');
|
|
||||||
};
|
|
||||||
const stop = () => {
|
|
||||||
Tone.getTransport().pause();
|
|
||||||
setStarted(false);
|
|
||||||
};
|
|
||||||
const toggle = () => (started ? stop() : start());
|
|
||||||
return {
|
|
||||||
start,
|
|
||||||
stop,
|
|
||||||
onEvent,
|
|
||||||
started,
|
|
||||||
setStarted,
|
|
||||||
toggle,
|
|
||||||
query,
|
|
||||||
activeCycle,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useCycle;
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
useRepl.mjs - <short description TODO>
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/useRepl.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 { useCallback, useState, useMemo } from 'react';
|
|
||||||
import { evaluate } from '@strudel.cycles/eval';
|
|
||||||
import useCycle from './useCycle.mjs';
|
|
||||||
import usePostMessage from './usePostMessage.mjs';
|
|
||||||
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
|
|
||||||
|
|
||||||
let s4 = () => {
|
|
||||||
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 {
|
|
||||||
onEvent?.(event);
|
|
||||||
if (event.context.logs?.length) {
|
|
||||||
event.context.logs.forEach(pushLog);
|
|
||||||
}
|
|
||||||
const { onTrigger = webaudioOutputTrigger } = event.context;
|
|
||||||
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],
|
|
||||||
),
|
|
||||||
onQuery: useCallback(
|
|
||||||
(state) => {
|
|
||||||
try {
|
|
||||||
return pattern?.query(state) || [];
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err);
|
|
||||||
err.message = 'query error: ' + err.message;
|
|
||||||
setError(err);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[pattern],
|
|
||||||
),
|
|
||||||
onSchedule: useCallback((_events, cycle) => logCycle(_events, cycle), []),
|
|
||||||
ready: !!pattern && !!activeCode,
|
|
||||||
});
|
|
||||||
|
|
||||||
const broadcast = usePostMessage(({ data: { from, type } }) => {
|
|
||||||
if (type === 'start' && from !== id) {
|
|
||||||
// console.log('message', from, type);
|
|
||||||
cycle.setStarted(false);
|
|
||||||
setActiveCode(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const activateCode = useCallback(
|
|
||||||
async (_code = code) => {
|
|
||||||
if (activeCode && !dirty) {
|
|
||||||
setError(undefined);
|
|
||||||
cycle.start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
setPending(true);
|
|
||||||
const parsed = await evaluate(_code);
|
|
||||||
cycle.start();
|
|
||||||
broadcast({ type: 'start', from: id });
|
|
||||||
setPattern(() => parsed.pattern);
|
|
||||||
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],
|
|
||||||
);
|
|
||||||
// logs events of cycle
|
|
||||||
const logCycle = (_events, cycle) => {
|
|
||||||
if (_events.length) {
|
|
||||||
// pushLog(`# cycle ${cycle}\n` + _events.map((e: any) => e.show()).join('\n'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const togglePlay = () => {
|
|
||||||
if (!cycle.started) {
|
|
||||||
activateCode();
|
|
||||||
} else {
|
|
||||||
cycle.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
hideHeader,
|
|
||||||
hideConsole,
|
|
||||||
pending,
|
|
||||||
code,
|
|
||||||
setCode,
|
|
||||||
pattern,
|
|
||||||
error,
|
|
||||||
cycle,
|
|
||||||
setPattern,
|
|
||||||
dirty,
|
|
||||||
log,
|
|
||||||
togglePlay,
|
|
||||||
setActiveCode,
|
|
||||||
activateCode,
|
|
||||||
activeCode,
|
|
||||||
pushLog,
|
|
||||||
hash,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useRepl;
|
|
||||||
@ -8,7 +8,9 @@ function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = fals
|
|||||||
const [evalError, setEvalError] = useState();
|
const [evalError, setEvalError] = useState();
|
||||||
const [activeCode, setActiveCode] = useState(code);
|
const [activeCode, setActiveCode] = useState(code);
|
||||||
const [pattern, setPattern] = useState();
|
const [pattern, setPattern] = useState();
|
||||||
|
const [started, setStarted] = useState(false);
|
||||||
const isDirty = code !== activeCode;
|
const isDirty = code !== activeCode;
|
||||||
|
// TODO: make sure this hook reruns when scheduler.started changes
|
||||||
const { scheduler, evaluate: _evaluate } = useMemo(
|
const { scheduler, evaluate: _evaluate } = useMemo(
|
||||||
() =>
|
() =>
|
||||||
repl({
|
repl({
|
||||||
@ -23,6 +25,7 @@ function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = fals
|
|||||||
setPattern(_pattern);
|
setPattern(_pattern);
|
||||||
setEvalError();
|
setEvalError();
|
||||||
},
|
},
|
||||||
|
onToggle: (v) => setStarted(v),
|
||||||
onEvalError: setEvalError,
|
onEvalError: setEvalError,
|
||||||
}),
|
}),
|
||||||
[defaultOutput, interval, getTime],
|
[defaultOutput, interval, getTime],
|
||||||
@ -32,12 +35,22 @@ function useStrudel({ defaultOutput, interval, getTime, code, evalOnMount = fals
|
|||||||
const inited = useRef();
|
const inited = useRef();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!inited.current && evalOnMount && code) {
|
if (!inited.current && evalOnMount && code) {
|
||||||
|
console.log('eval on mount');
|
||||||
inited.current = true;
|
inited.current = true;
|
||||||
evaluate();
|
evaluate();
|
||||||
}
|
}
|
||||||
}, [evaluate, evalOnMount, code]);
|
}, [evaluate, evalOnMount, code]);
|
||||||
|
|
||||||
return { schedulerError, scheduler, evalError, evaluate, activeCode, isDirty, pattern };
|
const togglePlay = async () => {
|
||||||
|
if (started) {
|
||||||
|
scheduler.pause();
|
||||||
|
// scheduler.stop();
|
||||||
|
} else {
|
||||||
|
await evaluate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { schedulerError, scheduler, evalError, evaluate, activeCode, isDirty, pattern, started, togglePlay };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useStrudel;
|
export default useStrudel;
|
||||||
|
|||||||
@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
export { default as CodeMirror, flash } from './components/CodeMirror6';
|
export { default as CodeMirror, flash } from './components/CodeMirror6';
|
||||||
export * from './components/MiniRepl';
|
export * from './components/MiniRepl';
|
||||||
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 useStrudel } from './hooks/useStrudel';
|
export { default as useStrudel } from './hooks/useStrudel';
|
||||||
export { default as useKeydown } from './hooks/useKeydown';
|
export { default as useKeydown } from './hooks/useKeydown';
|
||||||
export { default as cx } from './cx';
|
export { default as cx } from './cx';
|
||||||
|
|||||||
@ -24,3 +24,17 @@ cd repl
|
|||||||
npm run build # <- builds repl + tutorial to ../docs
|
npm run build # <- builds repl + tutorial to ../docs
|
||||||
npm run static # <- test static build
|
npm run static # <- test static build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Refactoring Notes
|
||||||
|
|
||||||
|
currently broken / buggy:
|
||||||
|
|
||||||
|
- MiniREPL
|
||||||
|
- repl log section
|
||||||
|
- hideHeader flag
|
||||||
|
- pending flag
|
||||||
|
- web midi
|
||||||
|
- draw / pianoroll
|
||||||
|
- pause does stop
|
||||||
|
- random button triggers start
|
||||||
|
- highlighting seems too late (off by latency ?)
|
||||||
|
|||||||
@ -4,9 +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 } from '@strudel.cycles/eval';
|
// import { evaluate } from '@strudel.cycles/eval';
|
||||||
import { CodeMirror, cx, flash, useHighlighting, useRepl, useWebMidi } from '@strudel.cycles/react';
|
import { CodeMirror, cx, flash, useHighlighting, useWebMidi } from '@strudel.cycles/react';
|
||||||
import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
|
// import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
|
||||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import logo from './logo.svg';
|
import logo from './logo.svg';
|
||||||
@ -17,6 +17,8 @@ import { resetLoadedSamples, getAudioContext } from '@strudel.cycles/webaudio';
|
|||||||
import { controls, evalScope } from '@strudel.cycles/core';
|
import { controls, evalScope } from '@strudel.cycles/core';
|
||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
import { useStrudel } from '@strudel.cycles/react';
|
||||||
|
import { webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
|
|
||||||
// Create a single supabase client for interacting with your database
|
// Create a single supabase client for interacting with your database
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
@ -25,11 +27,11 @@ const supabase = createClient(
|
|||||||
);
|
);
|
||||||
|
|
||||||
evalScope(
|
evalScope(
|
||||||
Tone,
|
// Tone,
|
||||||
controls, // sadly, this cannot be exported from core direclty
|
controls, // sadly, this cannot be exported from core direclty
|
||||||
{ WebDirt },
|
{ WebDirt },
|
||||||
import('@strudel.cycles/core'),
|
import('@strudel.cycles/core'),
|
||||||
import('@strudel.cycles/tone'),
|
// import('@strudel.cycles/tone'),
|
||||||
import('@strudel.cycles/tonal'),
|
import('@strudel.cycles/tonal'),
|
||||||
import('@strudel.cycles/mini'),
|
import('@strudel.cycles/mini'),
|
||||||
import('@strudel.cycles/midi'),
|
import('@strudel.cycles/midi'),
|
||||||
@ -42,6 +44,11 @@ evalScope(
|
|||||||
|
|
||||||
prebake();
|
prebake();
|
||||||
|
|
||||||
|
const pushLog = console.log;
|
||||||
|
const hideHeader = false;
|
||||||
|
const pending = false;
|
||||||
|
const getTime = () => getAudioContext().currentTime;
|
||||||
|
|
||||||
async function initCode() {
|
async function initCode() {
|
||||||
// load code from url hash (either short hash from database or decode long hash)
|
// load code from url hash (either short hash from database or decode long hash)
|
||||||
try {
|
try {
|
||||||
@ -83,38 +90,28 @@ const randomTune = getRandomTune();
|
|||||||
const isEmbedded = window.location !== window.parent.location;
|
const isEmbedded = window.location !== window.parent.location;
|
||||||
function App() {
|
function App() {
|
||||||
// const [editor, setEditor] = useState();
|
// const [editor, setEditor] = useState();
|
||||||
|
const [code, setCode] = useState('// LOADING');
|
||||||
const [view, setView] = useState();
|
const [view, setView] = useState();
|
||||||
const [lastShared, setLastShared] = useState();
|
const [lastShared, setLastShared] = useState();
|
||||||
const {
|
|
||||||
setCode,
|
const { scheduler, evaluate, schedulerError, evalError, isDirty, activeCode, pattern, started, togglePlay } =
|
||||||
setPattern,
|
useStrudel({
|
||||||
error,
|
code,
|
||||||
code,
|
defaultOutput: webaudioOutput,
|
||||||
cycle,
|
getTime,
|
||||||
dirty,
|
});
|
||||||
log,
|
const error = schedulerError || evalError;
|
||||||
togglePlay,
|
|
||||||
activeCode,
|
|
||||||
setActiveCode,
|
|
||||||
activateCode,
|
|
||||||
pattern,
|
|
||||||
pushLog,
|
|
||||||
pending,
|
|
||||||
hideHeader,
|
|
||||||
hideConsole,
|
|
||||||
} = useRepl({
|
|
||||||
tune: '// LOADING...',
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initCode().then((decoded) => setCode(decoded || randomTune));
|
initCode().then((decoded) => setCode(decoded || randomTune));
|
||||||
}, []);
|
}, []);
|
||||||
const logBox = useRef();
|
const logBox = useRef();
|
||||||
// scroll log box to bottom when log changes
|
// scroll log box to bottom when log changes
|
||||||
useLayoutEffect(() => {
|
|
||||||
|
/* useLayoutEffect(() => {
|
||||||
if (logBox.current) {
|
if (logBox.current) {
|
||||||
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
||||||
}
|
}
|
||||||
}, [log]);
|
}, [log]); */
|
||||||
|
|
||||||
// set active pattern on ctrl+enter
|
// set active pattern on ctrl+enter
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -124,25 +121,27 @@ function App() {
|
|||||||
if (e.code === 'Enter') {
|
if (e.code === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
flash(view);
|
flash(view);
|
||||||
await activateCode();
|
// await activateCode();
|
||||||
|
await evaluate();
|
||||||
} else if (e.code === 'Period') {
|
} else if (e.code === 'Period') {
|
||||||
cycle.stop();
|
scheduler.stop();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('keydown', handleKeyPress, true);
|
window.addEventListener('keydown', handleKeyPress, true);
|
||||||
return () => window.removeEventListener('keydown', handleKeyPress, true);
|
return () => window.removeEventListener('keydown', handleKeyPress, true);
|
||||||
}, [pattern, code, activateCode, cycle, view]);
|
}, [pattern /* , code */, /* activateCode, */ scheduler, view]);
|
||||||
|
|
||||||
useHighlighting({
|
useHighlighting({
|
||||||
view,
|
view,
|
||||||
pattern,
|
pattern,
|
||||||
active: cycle.started && !activeCode?.includes('strudel disable-highlighting'),
|
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => Tone.getTransport().seconds,
|
getTime: () => scheduler.phase,
|
||||||
|
// getTime: () => Tone.getTransport().seconds,
|
||||||
});
|
});
|
||||||
|
|
||||||
useWebMidi({
|
/* useWebMidi({
|
||||||
ready: useCallback(
|
ready: useCallback(
|
||||||
({ outputs }) => {
|
({ outputs }) => {
|
||||||
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `'${o.name}'`).join(' | ')}) to the pattern. `);
|
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `'${o.name}'`).join(' | ')}) to the pattern. `);
|
||||||
@ -161,7 +160,7 @@ function App() {
|
|||||||
},
|
},
|
||||||
[pushLog],
|
[pushLog],
|
||||||
),
|
),
|
||||||
});
|
}); */
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col">
|
<div className="min-h-screen flex flex-col">
|
||||||
@ -187,7 +186,7 @@ function App() {
|
|||||||
>
|
>
|
||||||
{!pending ? (
|
{!pending ? (
|
||||||
<span className={cx('flex items-center', isEmbedded ? 'w-16' : 'w-16')}>
|
<span className={cx('flex items-center', isEmbedded ? 'w-16' : 'w-16')}>
|
||||||
{cycle.started ? (
|
{started ? (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
@ -204,7 +203,7 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
{cycle.started ? 'pause' : 'play'}
|
{started ? 'pause' : 'play'}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<>loading...</>
|
<>loading...</>
|
||||||
@ -212,13 +211,13 @@ function App() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dirty && activateCode();
|
isDirty && activateCode();
|
||||||
pushLog('Code updated! Tip: You can also update the code by pressing ctrl+enter.');
|
pushLog('Code updated! Tip: You can also update the code by pressing ctrl+enter.');
|
||||||
}}
|
}}
|
||||||
className={cx(
|
className={cx(
|
||||||
'hover:bg-gray-300',
|
'hover:bg-gray-300',
|
||||||
!isEmbedded ? 'p-2' : 'px-2',
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
!dirty || !activeCode ? 'opacity-50' : '',
|
!isDirty || !activeCode ? 'opacity-50' : '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
🔄 update
|
🔄 update
|
||||||
@ -230,8 +229,8 @@ function App() {
|
|||||||
const _code = getRandomTune();
|
const _code = getRandomTune();
|
||||||
// console.log('tune', _code); // uncomment this to debug when random code fails
|
// console.log('tune', _code); // uncomment this to debug when random code fails
|
||||||
setCode(_code);
|
setCode(_code);
|
||||||
cleanupDraw();
|
/* cleanupDraw();
|
||||||
cleanupUi();
|
cleanupUi(); */
|
||||||
resetLoadedSamples();
|
resetLoadedSamples();
|
||||||
await prebake(); // declare default samples
|
await prebake(); // declare default samples
|
||||||
const parsed = await evaluate(_code);
|
const parsed = await evaluate(_code);
|
||||||
@ -307,7 +306,7 @@ function App() {
|
|||||||
{/* onCursor={markParens} */}
|
{/* onCursor={markParens} */}
|
||||||
<CodeMirror value={code} onChange={setCode} onViewChanged={setView} />
|
<CodeMirror value={code} onChange={setCode} onViewChanged={setView} />
|
||||||
<span className="z-[20] bg-black rounded-t-md py-1 px-2 fixed bottom-0 right-1 text-xs whitespace-pre text-right pointer-events-none">
|
<span className="z-[20] bg-black rounded-t-md py-1 px-2 fixed bottom-0 right-1 text-xs whitespace-pre text-right pointer-events-none">
|
||||||
{!cycle.started ? `press ctrl+enter to play\n` : dirty ? `ctrl+enter to update\n` : 'no changes\n'}
|
{!started ? `press ctrl+enter to play\n` : isDirty ? `ctrl+enter to update\n` : 'no changes\n'}
|
||||||
</span>
|
</span>
|
||||||
{error && (
|
{error && (
|
||||||
<div
|
<div
|
||||||
@ -320,7 +319,7 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isEmbedded && !hideConsole && (
|
{/* !isEmbedded && !hideConsole && (
|
||||||
<textarea
|
<textarea
|
||||||
className="z-[10] h-16 border-0 text-xs bg-[transparent] border-t border-slate-600 resize-none"
|
className="z-[10] h-16 border-0 text-xs bg-[transparent] border-t border-slate-600 resize-none"
|
||||||
value={log}
|
value={log}
|
||||||
@ -328,7 +327,7 @@ function App() {
|
|||||||
ref={logBox}
|
ref={logBox}
|
||||||
style={{ fontFamily: 'monospace' }}
|
style={{ fontFamily: 'monospace' }}
|
||||||
/>
|
/>
|
||||||
)}
|
) */}
|
||||||
</section>
|
</section>
|
||||||
{/* !isEmbedded && (
|
{/* !isEmbedded && (
|
||||||
<button className="fixed right-4 bottom-2 z-[11]" onClick={() => playStatic(code)}>
|
<button className="fixed right-4 bottom-2 z-[11]" onClick={() => playStatic(code)}>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ fetch('https://strudel.tidalcycles.org/EmuSP12.json')
|
|||||||
evalScope(
|
evalScope(
|
||||||
controls,
|
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'),
|
||||||
import('@strudel.cycles/mini'),
|
import('@strudel.cycles/mini'),
|
||||||
import('@strudel.cycles/midi'),
|
import('@strudel.cycles/midi'),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user