add repl-react + fix deps

This commit is contained in:
Felix Roos 2022-03-25 18:46:45 +01:00
parent 6f60a3a1d5
commit 6430f6207d
53 changed files with 33850 additions and 24809 deletions

View file

@ -1,6 +1,6 @@
import { Pattern, timeCat } from './strudel.mjs';
import bjork from 'bjork';
import { rotate } from '../../packages/core/util.mjs';
import { rotate } from './util.mjs';
import Fraction from './fraction.mjs';
const euclid = (pulses, steps, rotation = 0) => {

View file

@ -7,10 +7,10 @@ import '../../packages/xen/xen.mjs';
import '../../packages/xen/tune.mjs';
import '../core/euclid.mjs';
import euclid from '../core/euclid.mjs';
import '../repl-react/pianoroll.mjs';
import '../repl-react/draw.mjs';
import * as uiHelpers from '../repl-react/ui.mjs';
import * as drawHelpers from '../repl-react/draw.mjs';
import '@strudel/tone/pianoroll.mjs';
import '@strudel/tone/draw.mjs';
import * as uiHelpers from '@strudel/tone/ui.mjs';
import * as drawHelpers from '@strudel/tone/draw.mjs';
import gist from '../core/gist.js';
import shapeshifter from './shapeshifter.mjs';
import { mini } from '../mini/mini.mjs';

2365
packages/hooks/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
{
"name": "@strudel/hooks",
"version": "0.0.1",
"description": "React Hooks for strudel",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"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",
"dependencies": {
"tone": "^14.7.77",
"webmidi": "^3.0.15"
}
}

View file

@ -1,17 +1,17 @@
import { useEffect, useState } from 'react';
import type { ToneEventCallback } from 'tone';
import * as Tone from 'tone';
import { Hap, State, TimeSpan } from '../core/strudel.mjs';
import { State, TimeSpan } from '@strudel/core';
export declare interface UseCycleProps {
/* 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: UseCycleProps) {
function useCycle(props) {
// onX must use useCallback!
const { onEvent, onQuery, onSchedule, ready = true, onDraw } = props;
const [started, setStarted] = useState(false);

View file

@ -1,8 +1,8 @@
import { useCallback, useState, useMemo } from 'react';
import { evaluate } from '../../packages/eval/evaluate.mjs';
import { getPlayableNoteValue } from '../core/util.mjs';
import useCycle from './useCycle';
import usePostMessage from './usePostMessage';
import { evaluate } from '@strudel/eval';
import { getPlayableNoteValue } from '@strudel/core/util.mjs';
import useCycle from './useCycle.mjs';
import usePostMessage from './usePostMessage.mjs';
let s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
@ -10,7 +10,7 @@ let s4 = () => {
.substring(1);
};
function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any) {
function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState(tune);
const [activeCode, setActiveCode] = useState();
@ -40,15 +40,15 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any)
setError(undefined);
setActiveCode(_code);
setPending(false);
} catch (err: any) {
} catch (err) {
err.message = 'evaluation error: ' + err.message;
console.warn(err);
setError(err);
}
};
const pushLog = (message: string) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`);
const pushLog = (message) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`);
// logs events of cycle
const logCycle = (_events: any, cycle: any) => {
const logCycle = (_events, cycle) => {
if (_events.length) {
// pushLog(`# cycle ${cycle}\n` + _events.map((e: any) => e.show()).join('\n'));
}
@ -81,7 +81,7 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any)
} else {
onTrigger(time, event);
}
} catch (err: any) {
} 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
@ -93,7 +93,7 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any)
(state) => {
try {
return pattern?.query(state) || [];
} catch (err: any) {
} catch (err) {
console.warn(err);
err.message = 'query error: ' + err.message;
setError(err);

View file

@ -6,7 +6,7 @@ import * as Tone from 'tone';
const WebMidi = _WebMidi;
const Pattern = _Pattern;
export default function enableWebMidi() {
export function enableWebMidi() {
return new Promise((resolve, reject) => {
if (WebMidi.enabled) {
// if already enabled, just resolve WebMidi
@ -30,7 +30,7 @@ Pattern.prototype.midi = function (output, channel = 1) {
throw new Error(
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`
}')`,
);
}
return this._withEvent((event) => {
@ -59,7 +59,7 @@ Pattern.prototype.midi = function (output, channel = 1) {
throw new Error(
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
.map((o) => `'${o.name}'`)
.join(' | ')}`
.join(' | ')}`,
);
}
// console.log('midi', value, output);

View file

@ -0,0 +1,26 @@
{
"name": "@strudel/midi",
"version": "0.0.1",
"description": "Midi API for strudel",
"main": "midi.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"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"
}

View file

@ -1,6 +0,0 @@
.snowpack
build
node_modules
.DS_Store
.parcel-cache
oldtunes.ts

View file

@ -1,179 +0,0 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import CodeMirror, { markEvent, markParens } from './CodeMirror';
import cx from './cx';
import { evaluate } from '../eval/evaluate.mjs';
// import logo from './logo.svg';
import playStatic from './static.mjs';
import { defaultSynth } from '../tone/tone.mjs';
import * as tunes from '../tunes/tunes';
import useRepl from '../../repl/src/useRepl';
import { useWebMidi } from './useWebMidi';
// TODO: use https://www.npmjs.com/package/@monaco-editor/react
const [_, codeParam] = window.location.href.split('#');
let decoded;
try {
decoded = atob(decodeURIComponent(codeParam || ''));
} catch (err) {
console.warn('failed to decode', err);
}
function getRandomTune() {
const allTunes = Object.values(tunes);
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
return randomItem(allTunes);
}
const randomTune = getRandomTune();
function App() {
const [editor, setEditor] = useState();
const { setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog, pending } =
useRepl({
tune: decoded || randomTune,
defaultSynth,
onDraw: useCallback(markEvent(editor), [editor]),
});
const [uiHidden, setUiHidden] = useState(false);
const logBox = useRef();
// scroll log box to bottom when log changes
useLayoutEffect(() => {
logBox.current.scrollTop = logBox.current?.scrollHeight;
}, [log]);
// set active pattern on ctrl+enter
useLayoutEffect(() => {
// TODO: make sure this is only fired when editor has focus
const handleKeyPress = async (e) => {
if (e.ctrlKey || e.altKey) {
switch (e.code) {
case 'Enter':
await activateCode();
break;
case 'Period':
cycle.stop();
}
}
};
window.addEventListener('keydown', handleKeyPress);
return () => window.removeEventListener('keydown', handleKeyPress);
}, [pattern, code]);
useWebMidi({
ready: useCallback(({ outputs }) => {
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `'${o.name}'`).join(' | ')}) to the pattern. `);
}, []),
connected: useCallback(({ outputs }) => {
pushLog(`Midi device connected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`);
}, []),
disconnected: useCallback(({ outputs }) => {
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`);
}, []),
});
return (
<div className="min-h-screen flex flex-col">
<header
id="header"
className={cx(
'flex-none w-full h-14 px-2 flex border-b border-gray-200 justify-between z-[10]',
uiHidden ? 'bg-transparent text-white' : 'bg-white',
)}
>
<div className="flex items-center space-x-2">
{/* <img src={logo} className="Tidal-logo w-12 h-12" alt="logo" /> */}
<h1 className="text-2xl">Strudel REPL</h1>
</div>
<div className="flex space-x-4">
<button onClick={() => togglePlay()}>
{!pending ? (
<span className="flex items-center w-16">
{cycle.started ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
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"
clipRule="evenodd"
/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
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"
clipRule="evenodd"
/>
</svg>
)}
{cycle.started ? 'pause' : 'play'}
</span>
) : (
<>loading...</>
)}
</button>
<button
onClick={async () => {
const _code = getRandomTune();
console.log('tune', _code); // uncomment this to debug when random code fails
setCode(_code);
const parsed = await evaluate(_code);
setPattern(parsed.pattern);
}}
>
🎲 random
</button>
<button>
<a href="./tutorial">📚 tutorial</a>
</button>
<button onClick={() => setUiHidden((c) => !c)}>👀 {uiHidden ? 'show ui' : 'hide ui'}</button>
</div>
</header>
<section className="grow flex flex-col text-gray-100">
<div className="grow relative" id="code">
<div
className={cx(
'h-full transition-opacity',
error ? 'focus:ring-red-500' : 'focus:ring-slate-800',
uiHidden ? 'opacity-0' : 'opacity-100',
)}
>
<CodeMirror
value={code}
editorDidMount={setEditor}
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: false,
styleSelectedText: true,
cursorBlinkRate: 0,
}}
onCursor={markParens}
onChange={(_, __, value) => setCode(value)}
/>
<span className="p-4 absolute top-0 right-0 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'}
</span>
</div>
{error && (
<div className={cx('absolute right-2 bottom-2 px-2', 'text-red-500')}>
{error?.message || 'unknown error'}
</div>
)}
</div>
<textarea
className="z-[10] h-16 border-0 text-xs bg-[transparent] border-t border-slate-600 resize-none"
value={log}
readOnly
ref={logBox}
style={{ fontFamily: 'monospace' }}
/>
</section>
<button className="fixed right-4 bottom-2 z-[11]" onClick={() => playStatic(code)}>
static
</button>
</div>
);
}
export default App;

View file

@ -1,121 +0,0 @@
import React from 'react';
import { Controlled as CodeMirror2 } from 'react-codemirror2';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/pegjs/pegjs.js';
// import 'codemirror/theme/material.css';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
export default function CodeMirror({ value, onChange, onCursor, options, editorDidMount }) {
options = options || {
mode: 'javascript',
theme: 'material',
lineNumbers: true,
styleSelectedText: true,
cursorBlinkRate: 500,
};
return (
<CodeMirror2
value={value}
options={options}
onBeforeChange={onChange}
editorDidMount={editorDidMount}
onCursor={(editor, data) => onCursor?.(editor, data)}
/>
);
}
export const markEvent = (editor) => (time, event) => {
const locs = event.context.locations;
if (!locs || !editor) {
return;
}
const col = event.context?.color || '#FFCA28';
// mark active event
const marks = locs.map(({ start, end }) =>
editor.getDoc().markText(
{ line: start.line - 1, ch: start.column },
{ line: end.line - 1, ch: end.column },
//{ css: 'background-color: #FFCA28; color: black' } // background-color is now used by parent marking
{ css: 'outline: 1px solid ' + col + '; box-sizing:border-box' },
//{ css: `background-color: ${col};border-radius:5px` },
),
);
//Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler...
setTimeout(() => {
marks.forEach((mark) => mark.clear());
// }, '+' + event.duration * 0.5);
}, event.duration /* * 0.9 */ * 1000);
};
let parenMark;
export const markParens = (editor, data) => {
const v = editor.getDoc().getValue();
const marked = getCurrentParenArea(v, data);
parenMark?.clear();
parenMark = editor.getDoc().markText(...marked, { css: 'background-color: #00007720' }); //
};
// returns { line, ch } from absolute character offset
export function offsetToPosition(offset, code) {
const lines = code.split('\n');
let line = 0;
let ch = 0;
for (let i = 0; i < offset; i++) {
if (ch === lines[line].length) {
line++;
ch = 0;
} else {
ch++;
}
}
return { line, ch };
}
// returns absolute character offset from { line, ch }
export function positionToOffset(position, code) {
const lines = code.split('\n');
let offset = 0;
for (let i = 0; i < position.line; i++) {
offset += lines[i].length + 1;
}
offset += position.ch;
return offset;
}
// given code and caret position, the functions returns the indices of the parens we are in
export function getCurrentParenArea(code, caretPosition) {
const caret = positionToOffset(caretPosition, code);
let open, i, begin, end;
// walk left
i = caret;
open = 0;
while (i > 0) {
if (code[i - 1] === '(') {
open--;
} else if (code[i - 1] === ')') {
open++;
}
if (open === -1) {
break;
}
i--;
}
begin = i;
// walk right
i = caret;
open = 0;
while (i < code.length) {
if (code[i] === '(') {
open--;
} else if (code[i] === ')') {
open++;
}
if (open === 1) {
break;
}
i++;
}
end = i;
return [begin, end].map((o) => offsetToPosition(o, code));
}

View file

@ -1,22 +0,0 @@
# Strudel REPL
## Default Snowpack Readme
> ✨ Bootstrapped with Create Snowpack App (CSA).
## Available Scripts
### npm start
Runs the app in the development mode.
Open http://localhost:8080 to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
### npm run build
Builds a static copy of your site to the `docs/` folder.
Your app is ready to be deployed!
**For the best production performance:** Add a build bundler plugin like "@snowpack/plugin-webpack" to your `snowpack.config.mjs` config file.

View file

@ -1,3 +0,0 @@
export default function cx(...classes) { // : Array<string | undefined>
return classes.filter(Boolean).join(' ');
}

View file

@ -1,16 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
// Learn more: https://snowpack.dev/concepts/hot-module-replacement
if (import.meta.hot) {
import.meta.hot.accept();
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load diff

View file

@ -1,61 +0,0 @@
{
"scripts": {
"start": "snowpack dev --polyfill-node",
"build": "snowpack build --polyfill-node && cp ./public/.nojekyll ../docs && npm run build-tutorial",
"static": "npx serve ../docs",
"test": "web-test-runner \"src/**/*.test.tsx\"",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
"peggy": "peggy -o krill-parser.js --format es ./krill.pegjs",
"tutorial": "parcel src/tutorial/index.html --no-cache",
"build-tutorial": "parcel build src/tutorial/index.html --dist-dir ../docs/tutorial --public-url /tutorial --no-optimize --no-scope-hoist --no-cache"
},
"dependencies": {
"@tonaljs/tonal": "^4.6.5",
"@tonejs/piano": "^0.2.1",
"bjork": "^0.0.1",
"chord-voicings": "^0.0.1",
"codemirror": "^5.65.1",
"estraverse": "^5.3.0",
"events": "^3.3.0",
"multimap": "^1.1.0",
"ramda": "^0.28.0",
"react": "^17.0.2",
"react-codemirror2": "^7.2.1",
"react-dom": "^17.0.2",
"shift-ast": "^6.1.0",
"shift-codegen": "^7.0.3",
"shift-regexp-acceptor": "^2.0.3",
"shift-spec": "^2018.0.2",
"tone": "^14.7.77",
"tune.js": "^1.0.1",
"webmidi": "^2.5.2"
},
"devDependencies": {
"@mdx-js/react": "^1.6.22",
"@parcel/transformer-mdx": "^2.3.1",
"@snowpack/plugin-dotenv": "^2.1.0",
"@snowpack/plugin-postcss": "^1.4.3",
"@snowpack/plugin-react-refresh": "^2.5.0",
"@snowpack/plugin-typescript": "^1.2.1",
"@snowpack/web-test-runner-plugin": "^0.2.2",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.2",
"@testing-library/react": "^11.2.6",
"@types/chai": "^4.2.17",
"@types/mocha": "^8.2.2",
"@types/react": "^17.0.4",
"@types/react-dom": "^17.0.3",
"@types/snowpack-env": "^2.3.3",
"@web/test-runner": "^0.13.3",
"autoprefixer": "^10.4.2",
"chai": "^4.3.4",
"parcel": "^2.3.1",
"peggy": "^1.2.0",
"postcss": "^8.4.6",
"prettier": "^2.2.1",
"snowpack": "^3.3.7",
"tailwindcss": "^3.0.18",
"typescript": "^4.2.4"
}
}

View file

@ -1,8 +0,0 @@
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
// other plugins can go here, such as autoprefixer
},
};

View file

@ -1 +0,0 @@
strudel.tidalcycles.org

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,31 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: #2a3236;
}
.react-codemirror2,
.CodeMirror {
height: 100% !important;
background-color: transparent !important;
font-size: 15px;
z-index:20
}
.CodeMirror-line > span {
background-color: #2a323699;
}
.darken::before {
content: ' ';
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: block;
background: black;
opacity: 0.5;
}

View file

@ -1,45 +0,0 @@
// this file can be used to livecode from the comfort of your editor.
// just export a pattern from export default
// enable hot mode by pressing "toggle hot mode" on the top right of the repl
import { mini, h } from '../../mini/mini.mjs';
import { sequence, pure, reify, slowcat, fastcat, cat, stack, silence } from '../../packages/core/strudel.mjs';
import { gain, filter } from '../../tone/tone.mjs';
export default stack(
sequence(
mini(
'e5 [b4 c5] d5 [c5 b4]',
'a4 [a4 c5] e5 [d5 c5]',
'b4 [~ c5] d5 e5',
'c5 a4 a4 ~',
'[~ d5] [~ f5] a5 [g5 f5]',
'e5 [~ c5] e5 [d5 c5]',
'b4 [b4 c5] d5 e5',
'c5 a4 a4 ~'
)
.synth({
oscillator: { type: 'sine' },
envelope: { attack: 0.1 },
})
.rev()
),
sequence(
mini(
'e2 e3 e2 e3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 a2 a3',
'g#2 g#3 g#2 g#3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 b1 c2',
'd2 d3 d2 d3 d2 d3 d2 d3',
'c2 c3 c2 c3 c2 c3 c2 c3',
'b1 b2 b1 b2 e2 e3 e2 e3',
'a1 a2 a1 a2 a1 a2 a1 a2'
)
.synth({
oscillator: { type: 'sawtooth' },
envelope: { attack: 0.1 },
})
.chain(gain(0.7), filter(2000))
.rev()
)
).slow(16);

View file

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/global.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Strudel REPL" />
<title>Strudel REPL</title>
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="%PUBLIC_URL%/dist/index.js"></script>
</body>
</html>

View file

@ -1,41 +0,0 @@
/** @type {import("snowpack").SnowpackUserConfig } */
export default {
workspaceRoot: '..',
mount: {
public: { url: '/', static: false },
src: { url: '/dist' },
},
plugins: [
'@snowpack/plugin-postcss',
'@snowpack/plugin-react-refresh',
'@snowpack/plugin-dotenv',
[
'@snowpack/plugin-typescript',
{
/* Yarn PnP workaround: see https://www.npmjs.com/package/@snowpack/plugin-typescript */
...(process.versions.pnp ? { tsc: 'yarn pnpify tsc' } : {}),
},
],
],
routes: [
/* Enable an SPA Fallback in development: */
// {"match": "routes", "src": ".*", "dest": "/index.html"},
],
exclude: ['**/node_modules/**/*', '**/tutorial/**/*'],
optimize: {
/* Example: Bundle your final build: */
// "bundle": true,
},
packageOptions: {
/* ... */
knownEntrypoints: ['fraction.js', 'codemirror', 'shift-ast', 'ramda'],
},
devOptions: {
tailwindConfig: './tailwind.config.js',
},
buildOptions: {
/* ... */
out: '../docs',
baseUrl: '/',
},
};

View file

@ -1,66 +0,0 @@
import * as Tone from 'tone';
import { State, TimeSpan } from '../core/strudel.mjs';
import { getPlayableNoteValue } from '../../packages/core/util.mjs';
import { evaluate } from '../eval/evaluate.mjs';
// this is a test to play back events with as less runtime code as possible..
// the code asks for the number of seconds to prequery
// after the querying is done, the events are scheduled
// after the scheduling is done, the transport is started
// nothing happens while tone.js runs except the schedule callback, which is a thin wrapper around triggerAttackRelease calls
// so all glitches that appear here should have nothing to do with strudel and or the repl
async function playStatic(code) {
Tone.getTransport().cancel();
Tone.getTransport().stop();
let start, took;
const seconds = Number(prompt('How many seconds to run?')) || 60;
start = performance.now();
console.log('evaluating..');
const { pattern: pat } = await evaluate(code);
took = performance.now() - start;
console.log('evaluate took', took, 'ms');
console.log('querying..');
start = performance.now();
const events = pat
?.query(new State(new TimeSpan(0, seconds)))
?.filter((event) => event.part.begin.equals(event.whole.begin))
?.map((event) => ({
time: event.whole.begin.valueOf(),
duration: event.whole.end.sub(event.whole.begin).valueOf(),
value: event.value,
context: event.context,
}));
took = performance.now() - start;
console.log('query took', took, 'ms');
console.log('scheduling..');
start = performance.now();
events.forEach((event) => {
Tone.getTransport().schedule((time) => {
try {
const { onTrigger, velocity } = event.context;
if (!onTrigger) {
if (defaultSynth) {
const note = getPlayableNoteValue(event);
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
} else {
onTrigger(time, event);
}
} catch (err) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
console.error(err);
}
}, event.time);
});
took = performance.now() - start;
console.log('scheduling took', took, 'ms');
console.log('now starting!');
Tone.getTransport().start('+0.5');
}
export default playStatic;

View file

@ -1,8 +0,0 @@
module.exports = {
content: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx,mdx}'],
// specify other options here
theme: {
extend: {},
},
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
};

View file

@ -1,5 +1,5 @@
import * as Tone from 'tone';
import { Pattern } from '../core/strudel.mjs';
import { Pattern } from '@strudel/core';
export const getDrawContext = (id = 'test-canvas') => {
let canvas = document.querySelector('#' + id);

View file

@ -1,4 +1,4 @@
import { Pattern } from '../core/strudel.mjs';
import { Pattern } from '@strudel/core';
Pattern.prototype.pianoroll = function ({
timeframe = 10,

View file

@ -1,5 +1,6 @@
import { Pattern as _Pattern } from '../core/strudel.mjs';
import Tone from 'tone';
import { Pattern as _Pattern } from '@strudel/core';
import * as Tone from 'tone';
const {
AutoFilter,
Destination,
@ -20,8 +21,8 @@ const {
getDestination,
Players,
} = Tone;
/* import tonePiano from '@tonejs/piano';
const { Piano } = tonePiano; */
import * as tonePiano from '@tonejs/piano';
const { Piano } = tonePiano;
import { getPlayableNoteValue } from '../../packages/core/util.mjs';
// "balanced" | "interactive" | "playback";
@ -106,11 +107,11 @@ export const players = (options, baseUrl = '') => {
});
};
export const synth = (options) => new Synth(options);
/* export const piano = async (options = { velocities: 1 }) => {
export const piano = async (options = { velocities: 1 }) => {
const p = new Piano(options);
await p.load();
return p;
}; */
};
// effect helpers
export const vol = (v) => new Gain(v);