add static playback button

This commit is contained in:
Felix Roos 2022-03-18 19:48:12 +01:00
parent 8af4a92fc0
commit 88bcfc5e48
3 changed files with 74 additions and 24 deletions

View File

@ -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() {
<h1 className="text-2xl">Strudel REPL</h1>
</div>
<div className="flex space-x-4">
{/* <button
onClick={() => {
console.log('log..');
const start = performance.now();
const events = pattern?.query(new State(new TimeSpan(0, 100)));
const took = performance.now() - start;
console.log('took', took / 1000);
console.log('events', events);
}}
>
log 100s
</button> */}
<button onClick={() => togglePlay()}>
{!pending ? (
<span className="flex items-center w-16">
@ -139,7 +118,6 @@ function App() {
console.log('tune', _code); // uncomment this to debug when random code fails
setCode(_code);
const parsed = await evaluate(_code);
// Tone.Transport.cancel(Tone.Transport.seconds);
setPattern(parsed.pattern);
}}
>
@ -191,6 +169,7 @@ function App() {
style={{ fontFamily: 'monospace' }}
/>
</section>
<button className="fixed right-4 bottom-2 z-[11]" onClick={() => playStatic(code)}>static</button>
</div>
);
}

61
repl/src/static.mjs Normal file
View File

@ -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;

View File

@ -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/