diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs index 6e8171ea5..303525a25 100644 --- a/packages/core/cyclist.mjs +++ b/packages/core/cyclist.mjs @@ -74,6 +74,7 @@ export class Cyclist { stop() { logger('[cyclist] stop'); this.clock.stop(); + this.lastEnd = 0; this.setStarted(false); } setPattern(pat, autostart = false) { diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 308b76cee..7c96bb662 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -41,18 +41,7 @@ export function repl({ throw new Error('no code to evaluate'); } try { - beforeEval?.({ code }); - scheduler.setCps(1); // reset cps in case the code does not contain a setCps call - // problem: when the code does contain a setCps after an awaited promise, - // the cps will be 1 until the promise resolves - // example: - /* - await new Promise(resolve => setTimeout(resolve,1000)) - setCps(.5) - note("c a f e") - */ - // to make sure the setCps inside the code is called immediately, - // it has to be placed first + await beforeEval?.({ code }); let { pattern } = await _evaluate(code, transpiler); logger(`[eval] code updated`); diff --git a/packages/mini/krill-parser.js b/packages/mini/krill-parser.js index d2c861f20..2b0952db2 100644 --- a/packages/mini/krill-parser.js +++ b/packages/mini/krill-parser.js @@ -1511,18 +1511,20 @@ function peg$parse(input, options) { } function peg$parsemini() { - var s0, s1, s2, s3, s4; + var s0, s1, s2, s3, s4, s5, s6; s0 = peg$currPos; s1 = peg$parsews(); s2 = peg$parsequote(); if (s2 !== peg$FAILED) { - s3 = peg$parsestack_or_choose(); - if (s3 !== peg$FAILED) { - s4 = peg$parsequote(); - if (s4 !== peg$FAILED) { + s3 = peg$parsews(); + s4 = peg$parsestack_or_choose(); + if (s4 !== peg$FAILED) { + s5 = peg$parsews(); + s6 = peg$parsequote(); + if (s6 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f19(s3); + s0 = peg$f19(s4); } else { peg$currPos = s0; s0 = peg$FAILED; diff --git a/packages/mini/krill.pegjs b/packages/mini/krill.pegjs index 9ca48624b..72b453bea 100644 --- a/packages/mini/krill.pegjs +++ b/packages/mini/krill.pegjs @@ -182,7 +182,7 @@ polymeter_stack = head:sequence tail:stack_tail? // Experimental haskellish parser begins // mini-notation = a quoted stack -mini = ws quote sc:stack_or_choose quote +mini = ws quote ws sc:stack_or_choose ws quote { return sc; } // ------------------ operators --------------------------- diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index eea5a1831..c469bb124 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -83,7 +83,7 @@ export function MiniRepl({ e.preventDefault(); flash(view); await activateCode(); - } else if (e.code === 'Period') { + } else if (e.key === '.') { stop(); e.preventDefault(); } @@ -94,26 +94,6 @@ export function MiniRepl({ ), ); - // set active pattern on ctrl+enter - useLayoutEffect(() => { - if (enableKeyboard) { - 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); - } - }, [enableKeyboard, pattern, code, evaluate, stop, view]); - const [log, setLog] = useState([]); useLogger( useCallback((e) => { diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index 0900ebbfe..eee1bf7d2 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -46,9 +46,9 @@ function useStrudel({ drawContext, transpiler, editPattern, - beforeEval: ({ code }) => { + beforeEval: async ({ code }) => { setCode(code); - beforeEval?.(); + await beforeEval?.(); }, afterEval: (res) => { const { pattern: _pattern, code } = res; diff --git a/website/public/fonts/FiraCode/FiraCode-Regular.ttf b/website/public/fonts/FiraCode/FiraCode-Regular.ttf new file mode 100644 index 000000000..bd7368519 Binary files /dev/null and b/website/public/fonts/FiraCode/FiraCode-Regular.ttf differ diff --git a/website/public/fonts/FiraCode/FiraCode-SemiBold.ttf b/website/public/fonts/FiraCode/FiraCode-SemiBold.ttf new file mode 100644 index 000000000..d8dcef664 Binary files /dev/null and b/website/public/fonts/FiraCode/FiraCode-SemiBold.ttf differ diff --git a/website/public/fonts/FiraCode/LICENSE.txt b/website/public/fonts/FiraCode/LICENSE.txt new file mode 100644 index 000000000..805e0b38b --- /dev/null +++ b/website/public/fonts/FiraCode/LICENSE.txt @@ -0,0 +1,93 @@ +Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index c0b3af877..4b3399ad1 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -350,6 +350,8 @@ const fontFamilyOptions = { PressStart: 'PressStart2P', galactico: 'galactico', 'we-come-in-peace': 'we-come-in-peace', + FiraCode: 'FiraCode', + 'FiraCode-SemiBold': 'FiraCode SemiBold', }; function SettingsTab({ scheduler }) { diff --git a/website/src/repl/Loader.jsx b/website/src/repl/Loader.jsx new file mode 100644 index 000000000..0c629402a --- /dev/null +++ b/website/src/repl/Loader.jsx @@ -0,0 +1,13 @@ +import { cx } from '@strudel.cycles/react'; +import React from 'react'; + +function Loader({ active }) { + return ( +
+
+
+
+
+ ); +} +export default Loader; diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 186e16285..1f672b1bd 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -18,6 +18,7 @@ import * as tunes from './tunes.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import { themes } from './themes.mjs'; import { settingsMap, useSettings, setLatestCode } from '../settings.mjs'; +import Loader from './Loader'; const { latestCode } = settingsMap.get(); @@ -42,7 +43,7 @@ const modules = [ import('@strudel.cycles/csound'), ]; -evalScope( +const modulesLoading = evalScope( controls, // sadly, this cannot be exported from core direclty ...modules, ); @@ -102,25 +103,29 @@ export function Repl({ embedded = false }) { const isEmbedded = embedded || window.location !== window.parent.location; const [view, setView] = useState(); // codemirror view const [lastShared, setLastShared] = useState(); - const [pending, setPending] = useState(false); + const [pending, setPending] = useState(true); const { theme, keybindings, fontSize, fontFamily } = useSettings(); const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } = useStrudel({ - initialCode: '// LOADING', + initialCode: '// LOADING...', defaultOutput: webaudioOutput, getTime, - beforeEval: () => { + beforeEval: async () => { + setPending(true); + await modulesLoading; cleanupUi(); cleanupDraw(); - setPending(true); }, afterEval: ({ code }) => { setPending(false); setLatestCode(code); window.location.hash = '#' + encodeURIComponent(btoa(code)); }, + onEvalError: (err) => { + setPending(false); + }, onToggle: (play) => !play && cleanupDraw(false), drawContext, }); @@ -135,6 +140,7 @@ export function Repl({ embedded = false }) { 'highlight', ); setCode(decoded || latestCode || randomTune); + setPending(false); }); }, []); @@ -144,10 +150,14 @@ export function Repl({ embedded = false }) { async (e) => { if (e.ctrlKey || e.altKey) { if (e.code === 'Enter') { + if (getAudioContext().state !== 'running') { + alert('please click play to initialize the audio. you can use shortcuts after that!'); + return; + } e.preventDefault(); flash(view); await activateCode(); - } else if (e.code === 'Period') { + } else if (e.key === '.') { stop(); e.preventDefault(); } @@ -200,6 +210,7 @@ export function Repl({ embedded = false }) { logger(`[repl] ✨ loading random tune "${name}"`); clearCanvas(); resetLoadedSounds(); + scheduler.setCps(1); await prebake(); // declare default samples await evaluate(code, false); }; @@ -253,6 +264,7 @@ export function Repl({ embedded = false }) { // 'bg-gradient-to-t from-green-900 to-slate-900', // )} > +