add repl-react + fix deps
This commit is contained in:
parent
6f60a3a1d5
commit
6430f6207d
53 changed files with 33850 additions and 24809 deletions
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
2365
packages/hooks/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
30
packages/hooks/package.json
Normal file
30
packages/hooks/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
26
packages/midi/package.json
Normal file
26
packages/midi/package.json
Normal 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"
|
||||
}
|
||||
6
packages/repl-react/.gitignore
vendored
6
packages/repl-react/.gitignore
vendored
|
|
@ -1,6 +0,0 @@
|
|||
.snowpack
|
||||
build
|
||||
node_modules
|
||||
.DS_Store
|
||||
.parcel-cache
|
||||
oldtunes.ts
|
||||
|
|
@ -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;
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export default function cx(...classes) { // : Array<string | undefined>
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
|
|
@ -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 |
21647
packages/repl-react/package-lock.json
generated
21647
packages/repl-react/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
// postcss.config.js
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
// other plugins can go here, such as autoprefixer
|
||||
},
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
strudel.tidalcycles.org
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
|
|
@ -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: '/',
|
||||
},
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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')],
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Pattern } from '../core/strudel.mjs';
|
||||
import { Pattern } from '@strudel/core';
|
||||
|
||||
Pattern.prototype.pianoroll = function ({
|
||||
timeframe = 10,
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue