mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-25 12:38:35 +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 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 CodeMirror, { markEvent, markParens } from './CodeMirror';
|
||||||
import cx from './cx';
|
import cx from './cx';
|
||||||
import { evaluate } from './evaluate';
|
import { evaluate } from './evaluate';
|
||||||
import logo from './logo.svg';
|
import logo from './logo.svg';
|
||||||
import { useWebMidi } from './midi';
|
import { useWebMidi } from './midi';
|
||||||
|
import playStatic from './static.mjs';
|
||||||
|
import { defaultSynth } from './tone';
|
||||||
import * as tunes from './tunes';
|
import * as tunes from './tunes';
|
||||||
import useRepl from './useRepl';
|
import useRepl from './useRepl';
|
||||||
|
|
||||||
@ -18,15 +18,6 @@ try {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('failed to decode', 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() {
|
function getRandomTune() {
|
||||||
const allTunes = Object.values(tunes);
|
const allTunes = Object.values(tunes);
|
||||||
@ -95,18 +86,6 @@ function App() {
|
|||||||
<h1 className="text-2xl">Strudel REPL</h1>
|
<h1 className="text-2xl">Strudel REPL</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-4">
|
<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()}>
|
<button onClick={() => togglePlay()}>
|
||||||
{!pending ? (
|
{!pending ? (
|
||||||
<span className="flex items-center w-16">
|
<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
|
console.log('tune', _code); // uncomment this to debug when random code fails
|
||||||
setCode(_code);
|
setCode(_code);
|
||||||
const parsed = await evaluate(_code);
|
const parsed = await evaluate(_code);
|
||||||
// Tone.Transport.cancel(Tone.Transport.seconds);
|
|
||||||
setPattern(parsed.pattern);
|
setPattern(parsed.pattern);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -191,6 +169,7 @@ function App() {
|
|||||||
style={{ fontFamily: 'monospace' }}
|
style={{ fontFamily: 'monospace' }}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
<button className="fixed right-4 bottom-2 z-[11]" onClick={() => playStatic(code)}>static</button>
|
||||||
</div>
|
</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 { Piano } from '@tonejs/piano';
|
||||||
import { getPlayableNoteValue } from '../../util.mjs';
|
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
|
// what about
|
||||||
// https://www.charlie-roberts.com/gibberish/playground/
|
// https://www.charlie-roberts.com/gibberish/playground/
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user