import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; import CodeMirror, { markEvent, markParens } from './CodeMirror'; import cx from './cx'; import logo from './logo.svg'; import playStatic from './static.mjs'; import { defaultSynth } from '@strudel.cycles/tone'; import * as tunes from './tunes.mjs'; import useRepl from './useRepl.mjs'; import { useWebMidi } from './useWebMidi'; import './App.css'; // eval stuff start import { evaluate, extend } from '@strudel.cycles/eval'; import * as strudel from '@strudel.cycles/core/strudel.mjs'; import gist from '@strudel.cycles/core/gist.js'; import { mini } from '@strudel.cycles/mini/mini.mjs'; import { Tone } from '@strudel.cycles/tone'; import * as toneHelpers from '@strudel.cycles/tone/tone.mjs'; import * as voicingHelpers from '@strudel.cycles/tonal/voicings.mjs'; import * as uiHelpers from '@strudel.cycles/tone/ui.mjs'; import * as drawHelpers from '@strudel.cycles/tone/draw.mjs'; import euclid from '@strudel.cycles/core/euclid.mjs'; import '@strudel.cycles/tone/tone.mjs'; import '@strudel.cycles/midi/midi.mjs'; import '@strudel.cycles/tonal/voicings.mjs'; import '@strudel.cycles/tonal/tonal.mjs'; import '@strudel.cycles/xen/xen.mjs'; import '@strudel.cycles/xen/tune.mjs'; import '@strudel.cycles/core/euclid.mjs'; import '@strudel.cycles/tone/pianoroll.mjs'; import '@strudel.cycles/tone/draw.mjs'; import '@strudel.cycles/osc/osc.mjs'; extend(Tone, strudel, strudel.Pattern.prototype.bootstrap(), toneHelpers, voicingHelpers, drawHelpers, uiHelpers, { gist, euclid, mini, Tone, }); // eval stuff end const codeParam = window.location.href.split('#')[1]; let decoded; try { decoded = atob(decodeURIComponent(codeParam || '')); } catch (err) { console.warn('failed to decode', err); } function getRandomTune() { const allTunes = Object.values(tunes); const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; return randomItem(allTunes); } const randomTune = getRandomTune(); function App() { const [editor, setEditor] = useState(); const { setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog, pending } = useRepl({ tune: decoded || randomTune, defaultSynth, onDraw: useCallback((time, event) => markEvent(editor)(time, event), [editor]), }); const [uiHidden, setUiHidden] = useState(false); const logBox = useRef(); // scroll log box to bottom when log changes useLayoutEffect(() => { logBox.current.scrollTop = logBox.current?.scrollHeight; }, [log]); // set active pattern on ctrl+enter useLayoutEffect(() => { // TODO: make sure this is only fired when editor has focus const handleKeyPress = async (e) => { if (e.ctrlKey || e.altKey) { if (e.code === 'Enter') { await activateCode(); } else if (e.code === 'Period') { cycle.stop(); } } }; window.addEventListener('keydown', handleKeyPress); return () => window.removeEventListener('keydown', handleKeyPress); }, [pattern, code, activateCode, cycle]); useWebMidi({ ready: useCallback( ({ outputs }) => { pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `'${o.name}'`).join(' | ')}) to the pattern. `); }, [pushLog], ), connected: useCallback( ({ outputs }) => { pushLog(`Midi device connected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`); }, [pushLog], ), disconnected: useCallback( ({ outputs }) => { pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`); }, [pushLog], ), }); return (
setCode(value)} /> {!cycle.started ? `press ctrl+enter to play\n` : dirty ? `ctrl+enter to update\n` : 'no changes\n'}
{error && (
{error?.message || 'unknown error'}
)}