mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 21:58:37 +00:00
add static playback button
This commit is contained in:
parent
8af4a92fc0
commit
88bcfc5e48
@ -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
61
repl/src/static.mjs
Normal 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;
|
||||
@ -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/
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user