/* App.js - Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ // import { evaluate } from '@strudel.cycles/eval'; import { CodeMirror, cx, flash, useHighlighting } from '@strudel.cycles/react'; // import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone'; import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import './App.css'; import logo from './logo.svg'; import * as tunes from './tunes.mjs'; import { prebake } from './prebake.mjs'; import * as WebDirt from 'WebDirt'; import { resetLoadedSamples, getAudioContext } from '@strudel.cycles/webaudio'; import { controls, evalScope, logger } from '@strudel.cycles/core'; import { createClient } from '@supabase/supabase-js'; import { nanoid } from 'nanoid'; import { useStrudel } from '@strudel.cycles/react'; import { webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; initAudioOnFirstClick(); // Create a single supabase client for interacting with your database const supabase = createClient( 'https://pidxdsxphlhzjnzmifth.supabase.co', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', ); evalScope( // Tone, controls, // sadly, this cannot be exported from core direclty { WebDirt }, import('@strudel.cycles/core'), // import('@strudel.cycles/tone'), import('@strudel.cycles/tonal'), import('@strudel.cycles/mini'), import('@strudel.cycles/midi'), import('@strudel.cycles/xen'), import('@strudel.cycles/webaudio'), import('@strudel.cycles/osc'), import('@strudel.cycles/serial'), import('@strudel.cycles/soundfonts'), ); prebake(); const pushLog = (message) => logger(`%c${message}`, 'background-color: black;color:white;padding:4px;border-radius:15px'); const hideHeader = false; const pending = false; const getTime = () => getAudioContext().currentTime; async function initCode() { // load code from url hash (either short hash from database or decode long hash) try { const initialUrl = window.location.href; const hash = initialUrl.split('?')[1]?.split('#')?.[0]; const codeParam = window.location.href.split('#')[1]; // looking like https://strudel.tidalcycles.org/?J01s5i1J0200 (fixed hash length) if (codeParam) { // looking like https://strudel.tidalcycles.org/#ImMzIGUzIg%3D%3D (hash length depends on code length) return atob(decodeURIComponent(codeParam || '')); } else if (hash) { return supabase .from('code') .select('code') .eq('hash', hash) .then(({ data, error }) => { if (error) { console.warn('failed to load hash', err); } if (data.length) { console.log('load hash from database', hash); return data[0].code; } }); } } 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(); const isEmbedded = window.location !== window.parent.location; function App() { // const [editor, setEditor] = useState(); const [view, setView] = useState(); const [lastShared, setLastShared] = useState(); const { code, setCode, scheduler, evaluate, activateCode, error, isDirty, activeCode, pattern, started, togglePlay, stop, } = useStrudel({ initialCode: '// LOADING', defaultOutput: webaudioOutput, getTime, autolink: true, }); useEffect(() => { initCode().then((decoded) => setCode(decoded || randomTune)); }, []); const logBox = useRef(); // scroll log box to bottom when log changes /* useLayoutEffect(() => { if (logBox.current) { 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') { e.preventDefault(); flash(view); await activateCode(); } else if (e.code === 'Period') { stop(); e.preventDefault(); } } }; window.addEventListener('keydown', handleKeyPress, true); return () => window.removeEventListener('keydown', handleKeyPress, true); }, [activateCode, stop, view]); useHighlighting({ view, pattern, active: started && !activeCode?.includes('strudel disable-highlighting'), getTime: () => scheduler.getPhase(), // getTime: () => Tone.getTransport().seconds, }); return (
{!hideHeader && ( )}
{/* onCursor={markParens} */} {!started ? `press ctrl+enter to play\n` : isDirty ? `press ctrl+enter to update\n` : 'press ctrl+dot do stop\n'} {error && (
{error?.message || 'unknown error'}
)}
{/* !isEmbedded && !hideConsole && (