diff --git a/repl/src/App.tsx b/repl/src/App.tsx index 7feac52d..c0e23af8 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -1,11 +1,11 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; -import * as Tone from 'tone'; -import { State, TimeSpan } from '../../strudel.mjs'; import CodeMirror, { markEvent, markParens } from './CodeMirror'; import cx from './cx'; import { evaluate } from './evaluate'; import logo from './logo.svg'; import { useWebMidi } from './midi'; +import playStatic from './static.mjs'; +import { defaultSynth } from './tone'; import * as tunes from './tunes'; import useRepl from './useRepl'; @@ -18,15 +18,6 @@ try { } catch (err) { console.warn('failed to decode', err); } -// "balanced" | "interactive" | "playback"; -// Tone.setContext(new Tone.Context({ latencyHint: 'playback', lookAhead: 1 })); -const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.getDestination()); -defaultSynth.set({ - oscillator: { type: 'triangle' }, - envelope: { - release: 0.01, - }, -}); function getRandomTune() { const allTunes = Object.values(tunes); @@ -95,18 +86,6 @@ function App() {

Strudel REPL

- {/* */}
); } diff --git a/repl/src/static.mjs b/repl/src/static.mjs new file mode 100644 index 00000000..cd9ece9e --- /dev/null +++ b/repl/src/static.mjs @@ -0,0 +1,61 @@ +import * as Tone from 'tone'; +import { State, TimeSpan } from '../../strudel.mjs'; +import { getPlayableNoteValue } from '../../util.mjs'; +import { evaluate } from './evaluate'; + +// 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) { + let start, took; + const seconds = Number(prompt('How many seconds to run?')) || 60; + start = performance.now(); + const { pattern: pat } = await evaluate(code); + took = performance.now() - start; + console.log('evaluate took', took, 'ms'); + Tone.getTransport().stop(); + start = performance.now(); + const events = pat + ?.query(new State(new TimeSpan(0, seconds))) + ?.filter((event) => event.part.begin.valueOf() === event.whole.begin.valueOf()) + ?.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'); + 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; + pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event + } + }, event.time); + }); + took = performance.now() - start; + console.log('schedule took', took, 'ms'); + + Tone.getTransport().start('+0.5'); +} + +export default playStatic; diff --git a/repl/src/tone.ts b/repl/src/tone.ts index 3bcc11fa..0d71a0a1 100644 --- a/repl/src/tone.ts +++ b/repl/src/tone.ts @@ -22,6 +22,16 @@ import { import { Piano } from '@tonejs/piano'; import { getPlayableNoteValue } from '../../util.mjs'; +// "balanced" | "interactive" | "playback"; +// Tone.setContext(new Tone.Context({ latencyHint: 'playback', lookAhead: 1 })); +export const defaultSynth = new PolySynth().chain(new Gain(0.5), getDestination()); +defaultSynth.set({ + oscillator: { type: 'triangle' }, + envelope: { + release: 0.01, + }, +}); + // what about // https://www.charlie-roberts.com/gibberish/playground/