Merge branch 'main' into uzu

This commit is contained in:
Felix Roos 2025-06-18 21:26:51 +02:00
commit 28cd7c1681
No known key found for this signature in database
186 changed files with 3188 additions and 2138 deletions

View file

@ -1,6 +1,6 @@
/*
Repl.jsx - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.js>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/repl/src/App.js>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -93,7 +93,7 @@ export function Header({ context, embedded = false }) {
>
{!isEmbedded && <span>update</span>}
</button>
{!isEmbedded && (
{/* !isEmbedded && (
<button
title="shuffle"
className="hover:opacity-50 p-2 flex items-center space-x-1"
@ -101,7 +101,7 @@ export function Header({ context, embedded = false }) {
>
<span> shuffle</span>
</button>
)}
) */}
{!isEmbedded && (
<button
title="share"

View file

@ -142,21 +142,21 @@ function UserPatterns({ context }) {
</div>
<div className="overflow-auto h-full bg-background p-2 rounded-md">
{patternFilter === patternFilterName.user && (
<PatternButtons
onClick={(id) =>
updateCodeWindow(
context,
{ ...userPatterns[id], collection: userPattern.collection },
autoResetPatternOnChange,
)
}
patterns={userPatterns}
started={context.started}
activePattern={activePattern}
viewingPatternID={viewingPatternID}
/>
)}
{/* {patternFilter === patternFilterName.user && ( */}
<PatternButtons
onClick={(id) =>
updateCodeWindow(
context,
{ ...userPatterns[id], collection: userPattern.collection },
autoResetPatternOnChange,
)
}
patterns={userPatterns}
started={context.started}
activePattern={activePattern}
viewingPatternID={viewingPatternID}
/>
{/* )} */}
</div>
</div>
);
@ -250,6 +250,11 @@ export function PatternsTab({ context }) {
const { patternFilter } = useSettings();
return (
<div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full">
<UserPatterns context={context} />
</div>
);
/* return (
<div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full">
<ButtonGroup
value={patternFilter}
@ -263,5 +268,5 @@ export function PatternsTab({ context }) {
<PublicPatterns context={context} />
)}
</div>
);
); */
}

View file

@ -70,7 +70,7 @@ export function Reference() {
<ul>
{entry.params?.map(({ name, type, description }, i) => (
<li key={i}>
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
{name} : {type?.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
</li>
))}
</ul>

View file

@ -6,7 +6,7 @@ import { ButtonGroup } from './Forms.jsx';
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
import { confirmDialog } from '../../util.mjs';
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony } from '@strudel/webaudio';
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio';
function Checkbox({ label, value, onChange, disabled = false }) {
return (
@ -108,6 +108,7 @@ export function SettingsTab({ started }) {
audioEngineTarget,
togglePanelTrigger,
maxPolyphony,
multiChannelOrbits,
} = useSettings();
const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@ -162,6 +163,22 @@ export function SettingsTab({ started }) {
value={maxPolyphony ?? ''}
/>
</FormItem>
<FormItem>
<Checkbox
label="Multi Channel Orbits"
onChange={(cbEvent) => {
const val = cbEvent.target.checked;
confirmDialog(RELOAD_MSG).then((r) => {
if (r == true) {
settingsMap.setKey('multiChannelOrbits', val);
setMultiChannelOrbits(val);
return window.location.reload();
}
});
}}
value={multiChannelOrbits}
/>
</FormItem>
<FormItem label="Theme">
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
</FormItem>

View file

@ -15,17 +15,18 @@ export function WelcomeTab({ context }) {
<br />
<span className="underline">1. hit play</span> - <span className="underline">2. change something</span> -{' '}
<span className="underline">3. hit update</span>
<br />
If you don't like what you hear, try <span className="underline">shuffle</span>!
{/* <br />
If you don't like what you hear, try <span className="underline">shuffle</span>! */}
</p>
<p>
To learn more about what this all means, check out the{' '}
{/* To learn more about what this all means, check out the{' '} */}
To get started, check out the
<a href={`${baseNoTrailing}/workshop/getting-started/`} target="_blank">
interactive tutorial
</a>
. Also feel free to join the{' '}
<a href="https://discord.com/invite/HGEdXmRkzT" target="_blank">
tidalcycles discord channel
discord channel
</a>{' '}
to ask any questions, give feedback or just say hello.
</p>
@ -37,11 +38,11 @@ export function WelcomeTab({ context }) {
</a>
, which is a popular live coding language for music, written in Haskell. Strudel is free/open source software:
you can redistribute and/or modify it under the terms of the{' '}
<a href="https://github.com/tidalcycles/strudel/blob/main/LICENSE" target="_blank">
<a href="https://codeberg.org/uzu/strudel/src/branch/main/LICENSE" target="_blank">
GNU Affero General Public License
</a>
. You can find the source code at{' '}
<a href="https://github.com/tidalcycles/strudel" target="_blank">
<a href="https://codeberg.org/uzu/strudel" target="_blank">
github
</a>
. You can also find <a href="https://github.com/felixroos/dough-samples/blob/main/README.md">licensing info</a>{' '}

View file

@ -1,6 +1,6 @@
/*
tunes.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/tunes.mjs>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/repl/src/tunes.mjs>
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 <https://www.gnu.org/licenses/>.
*/

View file

@ -1,6 +1,6 @@
/*
Repl.jsx - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.js>
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/repl/src/App.js>
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 <https://www.gnu.org/licenses/>.
*/
@ -18,7 +18,7 @@ import { setVersionDefaultsFrom } from './util.mjs';
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
import { clearHydra } from '@strudel/hydra';
import { useCallback, useEffect, useRef, useState } from 'react';
import { settingsMap, useSettings } from '../settings.mjs';
import { parseBoolean, settingsMap, useSettings } from '../settings.mjs';
import {
setActivePattern,
setLatestCode,
@ -36,11 +36,15 @@ import './Repl.css';
import { setInterval, clearInterval } from 'worker-timers';
import { getMetadata } from '../metadata_parser';
const { latestCode, maxPolyphony, audioDeviceName } = settingsMap.get();
const { latestCode, maxPolyphony, audioDeviceName, multiChannelOrbits } = settingsMap.get();
let modulesLoading, presets, drawContext, clearCanvas, audioReady;
if (typeof window !== 'undefined') {
audioReady = initAudioOnFirstClick({ maxPolyphony, audioDeviceName });
audioReady = initAudioOnFirstClick({
maxPolyphony,
audioDeviceName,
multiChannelOrbits: parseBoolean(multiChannelOrbits),
});
modulesLoading = loadModules();
presets = prebake();
drawContext = getDrawContext();
@ -55,6 +59,8 @@ async function getModule(name) {
return modules.find((m) => m.packageName === name);
}
const initialCode = `// LOADING`;
export function useReplContext() {
const { isSyncEnabled, audioEngineTarget } = useSettings();
const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc;
@ -73,7 +79,7 @@ export function useReplContext() {
transpiler,
autodraw: false,
root: containerRef.current,
initialCode: '// LOADING',
initialCode,
pattern: silence,
drawTime,
drawContext,
@ -129,9 +135,10 @@ export function useReplContext() {
code = latestCode;
msg = `Your last session has been loaded!`;
} else {
const { code: randomTune, name } = await getRandomTune();
code = randomTune;
msg = `A random code snippet named "${name}" has been loaded!`;
/* const { code: randomTune, name } = await getRandomTune();
code = randomTune; */
code = '$: s("[bd <hh oh>]*2").bank("tr909").dec(.4)';
msg = `Default code has been loaded`;
}
editor.setCode(code);
setDocumentTitle(code);

View file

@ -7,7 +7,7 @@ import './Repl.css';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { $featuredPatterns, loadDBPatterns } from '@src/user_pattern_utils.mjs';
import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs';
// Create a single supabase client for interacting with your database
export const supabase = createClient(
@ -16,9 +16,9 @@ export const supabase = createClient(
);
let dbLoaded;
if (typeof window !== 'undefined') {
/* if (typeof window !== 'undefined') {
dbLoaded = loadDBPatterns();
}
} */
export async function initCode() {
// load code from url hash (either short hash from database or decode long hash)
@ -110,38 +110,56 @@ export function confirmDialog(msg) {
}
let lastShared;
export async function shareCode(codeToShare) {
// const codeToShare = activeCode || code;
if (lastShared === codeToShare) {
logger(`Link already generated!`, 'error');
return;
}
confirmDialog(
'Do you want your pattern to be public? If no, press cancel and you will get just a private link.',
).then(async (isPublic) => {
const hash = nanoid(12);
const shareUrl = window.location.origin + window.location.pathname + '?' + hash;
const { error } = await supabase.from('code_v1').insert([{ code: codeToShare, hash, ['public']: isPublic }]);
if (!error) {
lastShared = codeToShare;
// copy shareUrl to clipboard
if (isTauri()) {
await writeText(shareUrl);
} else {
await navigator.clipboard.writeText(shareUrl);
}
const message = `Link copied to clipboard: ${shareUrl}`;
alert(message);
// alert(message);
logger(message, 'highlight');
//RIP due to SPAM
// export async function shareCode(codeToShare) {
// // const codeToShare = activeCode || code;
// if (lastShared === codeToShare) {
// logger(`Link already generated!`, 'error');
// return;
// }
// confirmDialog(
// 'Do you want your pattern to be public? If no, press cancel and you will get just a private link.',
// ).then(async (isPublic) => {
// const hash = nanoid(12);
// const shareUrl = window.location.origin + window.location.pathname + '?' + hash;
// const { error } = await supabase.from('code_v1').insert([{ code: codeToShare, hash, ['public']: isPublic }]);
// if (!error) {
// lastShared = codeToShare;
// // copy shareUrl to clipboard
// if (isTauri()) {
// await writeText(shareUrl);
// } else {
// await navigator.clipboard.writeText(shareUrl);
// }
// const message = `Link copied to clipboard: ${shareUrl}`;
// alert(message);
// // alert(message);
// logger(message, 'highlight');
// } else {
// console.log('error', error);
// const message = `Error: ${error.message}`;
// // alert(message);
// logger(message);
// }
// });
// }
export async function shareCode() {
try {
const shareUrl = window.location.href;
if (isTauri()) {
await writeText(shareUrl);
} else {
console.log('error', error);
const message = `Error: ${error.message}`;
// alert(message);
logger(message);
await navigator.clipboard.writeText(shareUrl);
}
});
const message = `Link copied to clipboard!`;
alert(message);
logger(message, 'highlight');
} catch (e) {
console.error(e);
}
}
export const isIframe = () => window.location !== window.parent.location;