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/