This commit is contained in:
Jade (Rose) Rowland 2025-11-23 18:09:38 -05:00
parent f7f3aa7668
commit 6221099af8
9 changed files with 94 additions and 11 deletions

View file

@ -147,6 +147,7 @@ export function repl({
// set pattern methods that use this repl via closure // set pattern methods that use this repl via closure
const injectPatternMethods = () => { const injectPatternMethods = () => {
Pattern.prototype.p = function (id) { Pattern.prototype.p = function (id) {
if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) { if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) {
// allows muting a pattern x with x_ or _x // allows muting a pattern x with x_ or _x

View file

@ -8,3 +8,27 @@ export function ActionButton({ children, label, labelIsHidden, className, ...but
</button> </button>
); );
} }
export function ActionInput({ label, className, ...inputProps }) {
return (
<label className={cx("hover:opacity-50 cursor-pointer text-nowrap w-fit", className)}>
<input
style={{ display: 'none' }}
{...inputProps}
/>
{label}
</label>
);
}
export function SpecialActionButton(props) {
const { className, ...buttonProps } = props
return <ActionButton {...buttonProps} className={cx("bg-background p-2 max-w-[300px] rounded-md hover:opacity-50", className)} />
}
export function SpecialActionInput(props) {
const { className, ...inputProps } = props
return <ActionInput {...inputProps} className={cx("bg-background p-2 max-w-[300px] rounded-md hover:opacity-50", className)} />
}

View file

@ -0,0 +1,39 @@
import { errorLogger } from '@strudel/core';
import { useSettings, storeStartupScript } from '../../../settings.mjs';
import { SpecialActionInput } from '../button/action-button';
async function importScript(script) {
const reader = new FileReader()
reader.readAsText(script)
reader.onload = () => {
const text = reader.result;
console.info(text)
storeStartupScript(text)
};
reader.onerror = () => {
errorLogger(new Error('failed to import prebake script'), 'ImportPrebakeScriptButton')
}
}
export function ImportPrebakeScriptButton() {
const settings = useSettings();
return <SpecialActionInput type="file"
label="import prebake script"
accept=".strudel, .txt"
onChange={(e) => importScript(e.target.files[0])} />
// return <label className="hover:opacity-50 cursor-pointer">
// <input
// style={{ display: 'none' }}
// type="file"
// // multiple
// accept=".strudel, .txt"
// onChange={(e) => importScript(e.target.files[0])}
// />
// import prebake script
// </label>
}

View file

@ -127,7 +127,7 @@ function PanelContent({ context, tab }) {
case tabNames.reference: case tabNames.reference:
return <Reference />; return <Reference />;
case tabNames.settings: case tabNames.settings:
return <SettingsTab started={context.started} />; return <SettingsTab started={context.started} context={context} />;
case tabNames.files: case tabNames.files:
return <FilesTab />; return <FilesTab />;
default: default:

View file

@ -83,7 +83,7 @@ function UserPatterns({ context }) {
const activePattern = useActivePattern(); const activePattern = useActivePattern();
const viewingPatternStore = useViewingPatternData(); const viewingPatternStore = useViewingPatternData();
const viewingPatternData = parseJSON(viewingPatternStore); const viewingPatternData = parseJSON(viewingPatternStore);
const { userPatterns, patternFilter, patternAutoStart } = useSettings(); const { userPatterns, patternFilter, patternAutoStart, } = useSettings();
const viewingPatternID = viewingPatternData?.id; const viewingPatternID = viewingPatternData?.id;
return ( return (
<div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 "> <div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 ">

View file

@ -7,6 +7,8 @@ import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx'; import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
import { confirmDialog } from '../../util.mjs'; import { confirmDialog } from '../../util.mjs';
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio'; import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio';
import { ActionButton, SpecialActionButton } from '../button/action-button.jsx';
import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx';
function Checkbox({ label, value, onChange, disabled = false }) { function Checkbox({ label, value, onChange, disabled = false }) {
return ( return (
@ -86,7 +88,7 @@ const fontFamilyOptions = {
const RELOAD_MSG = 'Changing this setting requires the window to reload itself. OK?'; const RELOAD_MSG = 'Changing this setting requires the window to reload itself. OK?';
export function SettingsTab({ started }) { export function SettingsTab({ started,context }) {
const { const {
theme, theme,
keybindings, keybindings,
@ -113,6 +115,7 @@ export function SettingsTab({ started }) {
isTabIndentationEnabled, isTabIndentationEnabled,
isMultiCursorEnabled, isMultiCursorEnabled,
patternAutoStart, patternAutoStart,
startupScript,
} = useSettings(); } = useSettings();
const shouldAlwaysSync = isUdels(); const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@ -204,6 +207,8 @@ export function SettingsTab({ started }) {
/> />
</FormItem> </FormItem>
</div> </div>
<ImportPrebakeScriptButton />
{/* <SpecialActionButton label="edit startup script" onClick={() => context.editStartupScript(startupScript)}></SpecialActionButton> */}
<FormItem label="Keybindings"> <FormItem label="Keybindings">
<ButtonGroup <ButtonGroup
value={keybindings} value={keybindings}
@ -313,8 +318,7 @@ export function SettingsTab({ started }) {
</FormItem> </FormItem>
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem> <FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
<FormItem label="Reset Settings"> <FormItem label="Reset Settings">
<button <SpecialActionButton
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
onClick={() => { onClick={() => {
confirmDialog('Sure?').then((r) => { confirmDialog('Sure?').then((r) => {
if (r) { if (r) {
@ -325,7 +329,7 @@ export function SettingsTab({ started }) {
}} }}
> >
restore default settings restore default settings
</button> </SpecialActionButton>
</FormItem> </FormItem>
</div> </div>
); );

View file

@ -1,14 +1,23 @@
import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; import { Pattern, noteToMidi, valueToMidi } from '@strudel/core';
import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio';
import { registerSamplesFromDB } from './idbutils.mjs'; import { registerSamplesFromDB } from './idbutils.mjs';
import './piano.mjs'; import './piano.mjs';
import './files.mjs'; import './files.mjs';
import { settingsMap } from '@src/settings.mjs';
import { evaluate } from '@strudel/transpiler';
const { BASE_URL } = import.meta.env; const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
const baseCDN = 'https://strudel.b-cdn.net'; const baseCDN = 'https://strudel.b-cdn.net';
export async function prebake() { export async function prebake() {
// const settings = settingsMap.get()
// const prebakeScript = settings.startupScript || '';
// await evaluate(prebakeScript);
// https://archive.org/details/SalamanderGrandPianoV3 // https://archive.org/details/SalamanderGrandPianoV3
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm // License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
await Promise.all([ await Promise.all([

View file

@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core'; import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core';
import { getDrawContext } from '@strudel/draw'; import { getDrawContext } from '@strudel/draw';
import { transpiler } from '@strudel/transpiler'; import { evaluate, transpiler } from '@strudel/transpiler';
import { import {
getAudioContextCurrentTime, getAudioContextCurrentTime,
webaudioOutput, webaudioOutput,
@ -47,6 +47,7 @@ if (typeof window !== 'undefined') {
multiChannelOrbits: parseBoolean(multiChannelOrbits), multiChannelOrbits: parseBoolean(multiChannelOrbits),
}); });
modulesLoading = loadModules(); modulesLoading = loadModules();
// prebakeScript = evaluate(settingsMap.get().startupScript ?? '')
presets = prebake(); presets = prebake();
drawContext = getDrawContext(); drawContext = getDrawContext();
clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width);
@ -63,11 +64,10 @@ async function getModule(name) {
const initialCode = `// LOADING`; const initialCode = `// LOADING`;
export function useReplContext() { export function useReplContext() {
const { isSyncEnabled, audioEngineTarget } = useSettings(); const { isSyncEnabled, audioEngineTarget, startupScript } = useSettings();
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc; const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput; const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput;
const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds; const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds;
const init = useCallback(() => { const init = useCallback(() => {
const drawTime = [-2, 2]; const drawTime = [-2, 2];
const drawContext = getDrawContext(); const drawContext = getDrawContext();
@ -84,7 +84,9 @@ export function useReplContext() {
pattern: silence, pattern: silence,
drawTime, drawTime,
drawContext, drawContext,
prebake: async () => Promise.all([modulesLoading, presets]), prebake: async () => Promise.all([modulesLoading, presets,]).then(() => {
return evaluate(startupScript ?? '')
}),
onUpdateState: (state) => { onUpdateState: (state) => {
setReplState({ ...state }); setReplState({ ...state });
}, },
@ -200,6 +202,7 @@ export function useReplContext() {
} }
}; };
const handleEvaluate = () => { const handleEvaluate = () => {
editorRef.current.evaluate(); editorRef.current.evaluate();
}; };

View file

@ -45,6 +45,7 @@ export const defaultSettings = {
isPanelOpen: true, isPanelOpen: true,
togglePanelTrigger: 'click', //click | hover togglePanelTrigger: 'click', //click | hover
userPatterns: '{}', userPatterns: '{}',
startupScript: '//edit this script to run code on startup\n',
audioEngineTarget: audioEngineTargets.webaudio, audioEngineTarget: audioEngineTargets.webaudio,
isButtonRowHidden: false, isButtonRowHidden: false,
isCSSAnimationDisabled: false, isCSSAnimationDisabled: false,
@ -108,6 +109,8 @@ export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool); export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool); export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool);
export const storeStartupScript = (script) => settingsMap.setKey('startupScript', script);
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active); export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
const patternSetting = (key) => const patternSetting = (key) =>