diff --git a/repl/src/App.tsx b/repl/src/App.tsx index 375e29cd..d894ce69 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -4,16 +4,17 @@ import cx from './cx'; import * as Tone from 'tone'; import useCycle from './useCycle'; import type { Pattern } from './types'; -import * as tunes from './tunes'; +import defaultTune from './tunes'; import * as parser from './parse'; import CodeMirror from './CodeMirror'; import hot from '../public/hot'; import { isNote } from 'tone'; import { useWebMidi } from './midi'; - -const { tetris, tetrisRev, shapeShifted } = tunes; const { parse } = parser; +const [_, codeParam] = window.location.href.split('#'); +const decoded = atob(codeParam || ''); + const getHotCode = async () => { return fetch('/hot.js') .then((res) => res.text()) @@ -32,12 +33,17 @@ defaultSynth.set({ function App() { const [mode, setMode] = useState('javascript'); - const [code, setCode] = useState(shapeShifted); + const [code, setCode] = useState(decoded || defaultTune); const [log, setLog] = useState(''); const logBox = useRef(); const [error, setError] = useState(); const [pattern, setPattern] = useState(); const [activePattern, setActivePattern] = useState(); + const activatePattern = (_pattern = pattern) => { + setActivePattern(() => _pattern); + window.location.hash = '#' + btoa(code); + !cycle.started && cycle.start(); + }; const [isHot, setIsHot] = useState(false); // set to true to enable live coding in hot.js, using dev server const pushLog = (message: string) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`); // logs events of cycle @@ -86,8 +92,7 @@ function App() { useLayoutEffect(() => { const handleKeyPress = (e: any) => { if (e.ctrlKey && e.code === 'Enter') { - setActivePattern(() => pattern); - !cycle.started && cycle.start(); + activatePattern(); } }; document.addEventListener('keypress', handleKeyPress); @@ -104,7 +109,7 @@ function App() { setCode(_code); setMode('javascript'); }); // if using HMR, just use changed file - setActivePattern(hot); + activatePattern(hot); return; } else { _code = hot; @@ -117,8 +122,8 @@ function App() { // need arrow function here! otherwise if user returns a function, react will think it's a state reducer // only first time, then need ctrl+enter setPattern(() => parsed.pattern); - if (!activePattern || isHot) { - setActivePattern(() => parsed.pattern); + if (isHot) { + activatePattern(parsed.pattern); } setMode(parsed.mode); setError(undefined); @@ -196,7 +201,13 @@ function App() { diff --git a/repl/src/midi.ts b/repl/src/midi.ts index d27165b3..66a0af51 100644 --- a/repl/src/midi.ts +++ b/repl/src/midi.ts @@ -52,7 +52,7 @@ Pattern.prototype.midi = function (output: string, channel = 1) { // await enableWebMidi() device.playNote(value, channel, { time, - duration: event.duration * 1000, + duration: event.duration * 1000 - 5, // velocity: velocity ?? 0.5, velocity: 0.9, }); diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index 0d3b03d4..e0c1d2fb 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -34,7 +34,7 @@ export const shapeShifted = `stack( b1, b2, b1, b2, e2, e3, e2, e3, a1, a2, a1, a2, a1, a2, a1, a2, ).rev() -).slow(16).rev()`; +).slow(16)`; export const tetrisMidi = `${shapeShifted}.midi('IAC-Treiber Bus 1')`; @@ -192,3 +192,5 @@ export const whirlyStrudel = `mini("[e4 [b2 b3] c4]") .every(3, x => x.slow(1.5)) .fast(slowcat(1.25,1,1.5)) .every(2, _ => mini("e4 ~ e3 d4 ~"))`; + +export default shapeShifted;