From df865e323d18f55aa584ea081fa9e9cbfd44a03f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 5 Feb 2022 13:02:26 +0100 Subject: [PATCH] basic scheduling --- repl/package-lock.json | 76 ++++++++++++++++++++++++++++----- repl/package.json | 3 +- repl/src/App.tsx | 96 ++++++++++++++++++++++++++++-------------- repl/src/types.d.ts | 22 ++++++++++ repl/src/useCycle.ts | 73 ++++++++++++++++++++++++++++++++ strudel.mjs | 7 ++- 6 files changed, 232 insertions(+), 45 deletions(-) create mode 100644 repl/src/types.d.ts create mode 100644 repl/src/useCycle.ts diff --git a/repl/package-lock.json b/repl/package-lock.json index 988ca477..6ffdd90a 100644 --- a/repl/package-lock.json +++ b/repl/package-lock.json @@ -6,7 +6,8 @@ "": { "dependencies": { "react": "^17.0.2", - "react-dom": "^17.0.2" + "react-dom": "^17.0.2", + "tone": "^14.7.77" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.1.0", @@ -315,7 +316,6 @@ "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", "integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -2010,6 +2010,18 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "node_modules/automation-events": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-4.0.12.tgz", + "integrity": "sha512-7tu7q/rw3vFuAwjBoarCGql3V0HkC5QAbZUYhOblxxkHzdLUTjVotm0iaIRwiCSqxMeoFK64LrgaSCuSZguvGA==", + "dependencies": { + "@babel/runtime": "^7.16.7", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=12.20.1" + } + }, "node_modules/autoprefixer": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", @@ -6717,8 +6729,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/request": { "version": "2.88.2", @@ -7451,6 +7462,16 @@ "node": ">= 8" } }, + "node_modules/standardized-audio-context": { + "version": "25.3.20", + "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.20.tgz", + "integrity": "sha512-c6eMQXmN7iDS7ROuSqOrHQhxpazerJSnRHEJiKD8YkruZBTt/a5E7zmk+KkStoi0dohFAod8wvwWxc7S1gmdig==", + "dependencies": { + "@babel/runtime": "^7.17.0", + "automation-events": "^4.0.12", + "tslib": "^2.3.1" + } + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -7831,6 +7852,15 @@ "node": ">=0.6" } }, + "node_modules/tone": { + "version": "14.7.77", + "resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz", + "integrity": "sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==", + "dependencies": { + "standardized-audio-context": "^25.1.8", + "tslib": "^2.0.1" + } + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -7865,8 +7895,7 @@ "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -8592,7 +8621,6 @@ "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", "integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -10009,6 +10037,15 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "automation-events": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-4.0.12.tgz", + "integrity": "sha512-7tu7q/rw3vFuAwjBoarCGql3V0HkC5QAbZUYhOblxxkHzdLUTjVotm0iaIRwiCSqxMeoFK64LrgaSCuSZguvGA==", + "requires": { + "@babel/runtime": "^7.16.7", + "tslib": "^2.3.1" + } + }, "autoprefixer": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", @@ -13552,8 +13589,7 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "request": { "version": "2.88.2", @@ -14102,6 +14138,16 @@ "minipass": "^3.1.1" } }, + "standardized-audio-context": { + "version": "25.3.20", + "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.20.tgz", + "integrity": "sha512-c6eMQXmN7iDS7ROuSqOrHQhxpazerJSnRHEJiKD8YkruZBTt/a5E7zmk+KkStoi0dohFAod8wvwWxc7S1gmdig==", + "requires": { + "@babel/runtime": "^7.17.0", + "automation-events": "^4.0.12", + "tslib": "^2.3.1" + } + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -14397,6 +14443,15 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "tone": { + "version": "14.7.77", + "resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz", + "integrity": "sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==", + "requires": { + "standardized-audio-context": "^25.1.8", + "tslib": "^2.0.1" + } + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -14425,8 +14480,7 @@ "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "tsscmp": { "version": "1.0.6", diff --git a/repl/package.json b/repl/package.json index 9a3e2cc0..ee6f3cfd 100644 --- a/repl/package.json +++ b/repl/package.json @@ -8,7 +8,8 @@ }, "dependencies": { "react": "^17.0.2", - "react-dom": "^17.0.2" + "react-dom": "^17.0.2", + "tone": "^14.7.77" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.1.0", diff --git a/repl/src/App.tsx b/repl/src/App.tsx index bf7934c8..2572dddf 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -1,31 +1,69 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import logo from './logo.svg'; import * as strudel from '../../strudel.mjs'; import cx from './cx'; +import * as Tone from 'tone'; +import useCycle from './useCycle'; +import type { Hap, Pattern } from './types'; const { Fraction, TimeSpan } = strudel; const fr = (v: number) => new Fraction(v); const ts = (start: number, end: number) => new TimeSpan(fr(start), fr(end)); -const parse = (code: string): Pattern => { - const { sequence, stack, pure } = strudel; // make available to eval +const parse = (code: string): Pattern => { + const { sequence, stack, pure, slowcat, slow } = strudel; // make available to eval return eval(code); }; + +const synth = new Tone.Synth().toDestination(); + function App() { - const [code, setCode] = useState("sequence('a', 'b', sequence('c', 'd'))"); - const [events, setEvents] = useState[]>([]); + const [code, setCode] = useState( + // "sequence('c3', 'eb3', sequence('g3', 'f3'))" // + "slow(sequence('c3', 'eb3', sequence('g3', 'f3')), 'g3')" // + ); + const [log, setLog] = useState(''); + const logBox = useRef(); const [error, setError] = useState(); + const [pattern, setPattern] = useState(); + // logs events of cycle + const logCycle = (_events: any, cycle: any) => { + if (_events.length) { + setLog((log) => log + `${log ? '\n\n' : ''}# cycle ${cycle}\n` + _events.map((e: any) => e.show()).join('\n')); + } + }; + // cycle hook to control scheduling + const cycle = useCycle({ + onEvent: useCallback((time, event) => { + // console.log('event', event, time); + synth.triggerAttackRelease(event.value, event.duration, time); + }, []), + onQuery: useCallback((span) => pattern?.query(span) || [], [pattern]), + onSchedule: useCallback( + (_events, cycle) => { + // console.log('schedule', _events, cycle); + logCycle(_events, cycle); + }, + [pattern] + ), + ready: !!pattern, + }); + // parse pattern when code changes useEffect(() => { try { - const pattern = parse(code); - console.log('pattern', pattern); - setEvents(pattern.query(ts(0, 1))); - console.log('events', events); + const _pattern = parse(code); + setPattern(_pattern); + // cycle.query(cycle.activeCycle()); // reschedule active cycle setError(undefined); } catch (err: any) { setError(err); } }, [code]); + // scroll log box to bottom when log changes + useLayoutEffect(() => { + logBox.current.scrollTop = logBox.current?.scrollHeight; + }, [log]); + return (
@@ -38,34 +76,28 @@ function App() {