mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-06 23:05:24 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad7e301394 | |||
| 7acb9a3ac7 | |||
| 9665b233a1 |
@@ -11,6 +11,7 @@ import {
|
|||||||
keymap,
|
keymap,
|
||||||
lineNumbers,
|
lineNumbers,
|
||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
|
import {repeatCharKeymap} from './repeatcharacter.mjs';
|
||||||
import { persistentAtom } from '@nanostores/persistent';
|
import { persistentAtom } from '@nanostores/persistent';
|
||||||
import { logger, registerControl, repl } from '@strudel/core';
|
import { logger, registerControl, repl } from '@strudel/core';
|
||||||
import { cleanupDraw, cleanupDrawContext, Drawer } from '@strudel/draw';
|
import { cleanupDraw, cleanupDrawContext, Drawer } from '@strudel/draw';
|
||||||
@@ -111,6 +112,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
syntaxHighlighting(defaultHighlightStyle),
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
EditorView.updateListener.of((v) => onChange(v)),
|
EditorView.updateListener.of((v) => onChange(v)),
|
||||||
drawSelection({ cursorBlinkRate: 0 }),
|
drawSelection({ cursorBlinkRate: 0 }),
|
||||||
|
repeatCharKeymap,
|
||||||
Prec.highest(
|
Prec.highest(
|
||||||
keymap.of([
|
keymap.of([
|
||||||
{
|
{
|
||||||
@@ -215,7 +217,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/* {
|
/* {
|
||||||
key: 'Ctrl-Shift-.',
|
key: 'Ctrl-Shift-.',
|
||||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Compartment } from "@codemirror/state";
|
||||||
|
import { keymap } from "@codemirror/view";
|
||||||
|
|
||||||
|
const repeatMode = new Compartment();
|
||||||
|
|
||||||
|
function repeatPreviousChar(times) {
|
||||||
|
return ({ state, dispatch }) => {
|
||||||
|
const { from, empty } = state.selection.main;
|
||||||
|
if (!empty || from === 0) return false;
|
||||||
|
|
||||||
|
const prevChar = state.doc.sliceString(from - 1, from);
|
||||||
|
const text = Array(times).fill(prevChar).join(" ");
|
||||||
|
|
||||||
|
dispatch(state.update({
|
||||||
|
changes: {
|
||||||
|
from,
|
||||||
|
insert: " " + text
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitRepeatMode(view) {
|
||||||
|
view.dispatch({
|
||||||
|
effects: repeatMode.reconfigure([])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const digitBindings = Array.from({ length: 9 }, (_, i) => ({
|
||||||
|
key: String(i + 1),
|
||||||
|
run(view) {
|
||||||
|
repeatPreviousChar(i + 1)(view);
|
||||||
|
exitRepeatMode(view);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const repeatCharKeymap = [
|
||||||
|
|
||||||
|
repeatMode.of([]),
|
||||||
|
|
||||||
|
keymap.of([
|
||||||
|
{
|
||||||
|
key: "Alt-r",
|
||||||
|
run(view) {
|
||||||
|
view.dispatch({
|
||||||
|
effects: repeatMode.reconfigure(keymap.of(digitBindings))
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
];
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
onceEnded,
|
onceEnded,
|
||||||
releaseAudioNode,
|
releaseAudioNode,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { errorLogger, logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||||
const loadCache = {}; // string: Promise<ArrayBuffer>
|
const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||||
@@ -228,31 +228,22 @@ export async function fetchSampleMap(url) {
|
|||||||
}
|
}
|
||||||
url = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`;
|
url = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = getBaseURL(url);
|
|
||||||
if (typeof fetch !== 'function') {
|
if (typeof fetch !== 'function') {
|
||||||
errorLogger(new Error(`fetch is not supported in this environment. Skipping map load for: ${url}`), 'sampler.mjs')
|
// not a browser
|
||||||
return [{}, base || ''];
|
return;
|
||||||
}
|
}
|
||||||
|
const base = getBaseURL(url);
|
||||||
|
if (typeof fetch === 'undefined') {
|
||||||
try {
|
// skip fetch when in node / testing
|
||||||
const res = await fetch(url);
|
return;
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${res.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
return [json, json._base || base];
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
// Catching the failure here prevents it from bubbling up and crashing upstream
|
|
||||||
errorLogger(new Error(`Failed to fetch or parse sample map at "${url}":`), "sampler.mjs");
|
|
||||||
|
|
||||||
// Return a safe fallback structure so destructuring (e.g., [json, base]) won't break
|
|
||||||
return [{}, base || ''];
|
|
||||||
}
|
}
|
||||||
|
const json = await fetch(url)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error(`error loading "${url}"`);
|
||||||
|
});
|
||||||
|
return [json, json._base || base];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -963,9 +963,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
let url;
|
let url;
|
||||||
let sample = getSound(ir);
|
let sample = getSound(ir);
|
||||||
if (Array.isArray(sample)) {
|
if (Array.isArray(sample)) {
|
||||||
url = sample.data.samples[i % sample.data.samples.length].url;
|
url = sample.data.samples[i % sample.data.samples.length];
|
||||||
} else if (typeof sample === 'object') {
|
} else if (typeof sample === 'object') {
|
||||||
url = Object.values(sample.data.samples).flat()[i % Object.values(sample.data.samples).length].url;
|
url = Object.values(sample.data.samples).flat()[i % Object.values(sample.data.samples).length];
|
||||||
}
|
}
|
||||||
roomIR = await loadBuffer(url, ac, ir, 0);
|
roomIR = await loadBuffer(url, ac, ir, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ import {
|
|||||||
userPattern,
|
userPattern,
|
||||||
} from '../../../user_pattern_utils.mjs';
|
} from '../../../user_pattern_utils.mjs';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { getMetadata } from '../../../metadata_parser.js';
|
|
||||||
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
||||||
import { parseJSON, isUdels } from '../../util.mjs';
|
|
||||||
import { useSettings } from '../../../settings.mjs';
|
import { useSettings } from '../../../settings.mjs';
|
||||||
import { ActionButton } from '../button/action-button.jsx';
|
import { ActionButton } from '../button/action-button.jsx';
|
||||||
import { Pagination } from '../pagination/Pagination.jsx';
|
import { Pagination } from '../pagination/Pagination.jsx';
|
||||||
@@ -20,8 +18,13 @@ import { useDebounce } from '../usedebounce.jsx';
|
|||||||
import cx from '@src/cx.mjs';
|
import cx from '@src/cx.mjs';
|
||||||
import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx';
|
import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx';
|
||||||
|
|
||||||
|
const PATTERN_SORT = {
|
||||||
|
"NEWEST": "most recent",
|
||||||
|
"A-Z": "A-Z",
|
||||||
|
}
|
||||||
|
|
||||||
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
||||||
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
const meta = pattern.meta
|
||||||
|
|
||||||
let title = meta.title;
|
let title = meta.title;
|
||||||
if (title == null) {
|
if (title == null) {
|
||||||
@@ -58,6 +61,10 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
|
|||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
{Object.values(patterns)
|
{Object.values(patterns)
|
||||||
|
.sort((a, b) => {
|
||||||
|
return (b.meta.title ?? "").localeCompare(a.meta.title ?? "")
|
||||||
|
|
||||||
|
})
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((pattern) => {
|
.map((pattern) => {
|
||||||
const id = pattern.id;
|
const id = pattern.id;
|
||||||
@@ -93,7 +100,7 @@ export function PatternsTab({ context }) {
|
|||||||
}
|
}
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(userPatterns).filter(([_key, pattern]) => {
|
Object.entries(userPatterns).filter(([_key, pattern]) => {
|
||||||
const meta = getMetadata(pattern.code);
|
const meta = pattern.meta;
|
||||||
|
|
||||||
// Search for specific meta keys
|
// Search for specific meta keys
|
||||||
const searchLowercaseTrimmed = search.trim().toLowerCase();
|
const searchLowercaseTrimmed = search.trim().toLowerCase();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { register } from '@strudel/core';
|
import { register } from '@strudel/core';
|
||||||
import { isUdels } from './repl/util.mjs';
|
import { isUdels } from './repl/util.mjs';
|
||||||
import { computed } from 'nanostores';
|
import { computed } from 'nanostores';
|
||||||
|
import { getMetadata } from './metadata_parser';
|
||||||
|
|
||||||
export const audioEngineTargets = {
|
export const audioEngineTargets = {
|
||||||
webaudio: 'webaudio',
|
webaudio: 'webaudio',
|
||||||
@@ -18,6 +19,11 @@ export const soundFilterType = {
|
|||||||
ALL: 'all',
|
ALL: 'all',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PATTERN_SORT = {
|
||||||
|
"NEWEST": "most recent",
|
||||||
|
"A-Z": "A-Z",
|
||||||
|
}
|
||||||
|
|
||||||
export const defaultSettings = {
|
export const defaultSettings = {
|
||||||
activeFooter: 'intro',
|
activeFooter: 'intro',
|
||||||
keybindings: 'codemirror',
|
keybindings: 'codemirror',
|
||||||
@@ -71,6 +77,7 @@ export const $settings = computed(settingsMap, (state) => {
|
|||||||
Object.keys(userPatterns).forEach((key) => {
|
Object.keys(userPatterns).forEach((key) => {
|
||||||
const data = userPatterns[key];
|
const data = userPatterns[key];
|
||||||
data.id = data.id ?? key;
|
data.id = data.id ?? key;
|
||||||
|
data.meta = getMetadata(data.code)
|
||||||
userPatterns[key] = data;
|
userPatterns[key] = data;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user