mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-01 13:29:25 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12fac86012 | |||
| cea6301ffd |
@@ -1,44 +0,0 @@
|
||||
name: Build and Deploy hot PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "warm-pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.event.label.name == 'serve-hot' }}
|
||||
runs-on: docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Deploy
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
eval $(ssh-agent -s)
|
||||
echo "$SSH_PRIVATE_KEY" | ssh-add -
|
||||
apt update && apt install -y rsync
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts
|
||||
rsync -atv --delete --delete-after --progress \
|
||||
./website/dist/ \
|
||||
strudel@matrix.toplap.org:/home/strudel/deploy/pr-${{ github.event.pull_request.number }}.hot.strudel.cc
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
keymap,
|
||||
lineNumbers,
|
||||
} from '@codemirror/view';
|
||||
import {repeatCharKeymap} from './repeatcharacter.mjs';
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
import { logger, registerControl, repl } from '@strudel/core';
|
||||
import { cleanupDraw, cleanupDrawContext, Drawer } from '@strudel/draw';
|
||||
@@ -21,16 +20,11 @@ import { evalBlock } from './block_utilities.mjs';
|
||||
import { flash, isFlashEnabled } from './flash.mjs';
|
||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||
import { keybindings } from './keybindings.mjs';
|
||||
import { jumpToCharacter } from './labelJump.mjs';
|
||||
import { getSliderWidgets, sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
||||
import { activateTheme, initTheme, theme } from './themes.mjs';
|
||||
import { isTooltipEnabled } from './tooltip.mjs';
|
||||
import { getActiveWidgets, updateWidgets, widgetPlugin } from './widget.mjs';
|
||||
import {
|
||||
deleteAllInlineBeforeCharacter,
|
||||
InsertCharBeforeChar,
|
||||
jumpToCharacter,
|
||||
jumpToNextCharacter,
|
||||
} from './labelJump.mjs';
|
||||
|
||||
export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands';
|
||||
|
||||
@@ -50,9 +44,9 @@ export const extensions = {
|
||||
isMultiCursorEnabled: (on) =>
|
||||
on
|
||||
? [
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
|
||||
]
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
|
||||
]
|
||||
: [],
|
||||
};
|
||||
export const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||
@@ -81,10 +75,6 @@ export const codemirrorSettings = persistentAtom('codemirror-settings', defaultS
|
||||
decode: JSON.parse,
|
||||
});
|
||||
|
||||
const ANON_LABEL = '$';
|
||||
const SOLO_LABEL = 'S';
|
||||
const MUTE_LABEL = '_';
|
||||
|
||||
// https://codemirror.net/docs/guide/
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo, strudelMirror }) {
|
||||
const settings = codemirrorSettings.get();
|
||||
@@ -112,7 +102,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
EditorView.updateListener.of((v) => onChange(v)),
|
||||
drawSelection({ cursorBlinkRate: 0 }),
|
||||
repeatCharKeymap,
|
||||
Prec.highest(
|
||||
keymap.of([
|
||||
{
|
||||
@@ -149,74 +138,12 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
||||
},
|
||||
{
|
||||
key: 'Alt-w',
|
||||
run: (view) => jumpToNextCharacter(view, ANON_LABEL, 1),
|
||||
run: (view) => jumpToCharacter(view, '$', 1),
|
||||
},
|
||||
{
|
||||
key: 'Alt-q',
|
||||
run: (view) => {
|
||||
return jumpToNextCharacter(view, ANON_LABEL, -1);
|
||||
},
|
||||
run: (view) => jumpToCharacter(view, '$', -1),
|
||||
},
|
||||
// clear all muted
|
||||
{
|
||||
key: `Alt-Ctrl-0`,
|
||||
run: (view) => {
|
||||
return deleteAllInlineBeforeCharacter(view, MUTE_LABEL + ANON_LABEL);
|
||||
},
|
||||
},
|
||||
// clear all solod
|
||||
{
|
||||
key: `Alt-Shift-0`,
|
||||
run: (view) => {
|
||||
return deleteAllInlineBeforeCharacter(view, SOLO_LABEL + ANON_LABEL);
|
||||
},
|
||||
},
|
||||
// clear all solo and mute
|
||||
{
|
||||
key: `Ctrl-Shift-0`,
|
||||
run: (view) => {
|
||||
return deleteAllInlineBeforeCharacter(view, ANON_LABEL);
|
||||
},
|
||||
},
|
||||
...Array.from({ length: 9 }).map((_, i) => {
|
||||
let num = i + 1;
|
||||
return {
|
||||
key: `Alt-${num}`,
|
||||
run: (view) => {
|
||||
return jumpToCharacter(view, ANON_LABEL, i);
|
||||
},
|
||||
};
|
||||
}),
|
||||
// handle solo toggles 1-9
|
||||
...Array.from({ length: 9 }).map((_, i) => {
|
||||
let num = i + 1;
|
||||
return {
|
||||
key: `Alt-Shift-${num}`,
|
||||
run: (view) => {
|
||||
return InsertCharBeforeChar(view, ANON_LABEL, SOLO_LABEL, i);
|
||||
},
|
||||
};
|
||||
}),
|
||||
// handle mute toggles 1-9
|
||||
...Array.from({ length: 9 }).map((_, i) => {
|
||||
let num = i + 1;
|
||||
return {
|
||||
key: `Alt-Ctrl-${num}`,
|
||||
run: (view) => {
|
||||
return InsertCharBeforeChar(view, ANON_LABEL, MUTE_LABEL, i);
|
||||
},
|
||||
};
|
||||
}),
|
||||
// Handle clearing mutes and solos 1-9
|
||||
...Array.from({ length: 9 }).map((_, i) => {
|
||||
let num = i + 1;
|
||||
return {
|
||||
key: `Ctrl-Shift-${num}`,
|
||||
run: (view) => {
|
||||
return InsertCharBeforeChar(view, ANON_LABEL, '', i);
|
||||
},
|
||||
};
|
||||
}),
|
||||
/* {
|
||||
key: 'Ctrl-Shift-.',
|
||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||
|
||||
@@ -1,54 +1,18 @@
|
||||
import { EditorSelection } from '@codemirror/state';
|
||||
import { SearchCursor } from '@codemirror/search';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
|
||||
/**
|
||||
* gets all of the positions of a character in a document, excluding commented out lines
|
||||
* @param { EditorState} state
|
||||
* @param {String} character
|
||||
* @returns {number[]}
|
||||
*/
|
||||
function getCharacterPositions(state, character) {
|
||||
const cursor = new SearchCursor(state.doc, character);
|
||||
|
||||
const characterPositions = [];
|
||||
while (!cursor.next().done) {
|
||||
|
||||
const linestartpos = state.doc.lineAt(cursor.value.to).from
|
||||
if (!isLineCommentedOut(state, linestartpos)) {
|
||||
|
||||
characterPositions.push(cursor.value.to);
|
||||
}
|
||||
}
|
||||
return characterPositions;
|
||||
}
|
||||
|
||||
function isLineCommentedOut(state, pos) {
|
||||
|
||||
const line = state.doc.lineAt(pos);
|
||||
// remove white space
|
||||
pos = line.from + line.text.search(/\S/)
|
||||
|
||||
const tree = syntaxTree(state);
|
||||
const node = tree.resolveInner(pos, 1)
|
||||
return node.name.includes("Comment")
|
||||
}
|
||||
|
||||
/**
|
||||
* jump to the next character in a document
|
||||
* @param {EditorView} view
|
||||
* @param {String} character
|
||||
* @param {number} direction 0 or 1
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function jumpToNextCharacter(view, character, direction = 1) {
|
||||
export function jumpToCharacter(view, character, direction = 1) {
|
||||
const { state, dispatch } = view;
|
||||
const pos = state.selection.main.head;
|
||||
const cursor = new SearchCursor(state.doc, character);
|
||||
|
||||
let characterPositions = [];
|
||||
let jumpPos;
|
||||
const characterPositions = getCharacterPositions(state, character);
|
||||
while (!cursor.next().done) {
|
||||
characterPositions.push(cursor.value.to);
|
||||
}
|
||||
if (!characterPositions.length) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if (direction > 0) {
|
||||
jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience
|
||||
@@ -57,97 +21,11 @@ export function jumpToNextCharacter(view, character, direction = 1) {
|
||||
}
|
||||
|
||||
if (jumpPos == null) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
const selection = EditorSelection.cursor(jumpPos - 1);
|
||||
dispatch({
|
||||
selection,
|
||||
effects: EditorView.scrollIntoView(
|
||||
selection.head,
|
||||
{ y: "start" }
|
||||
)
|
||||
selection: EditorSelection.cursor(jumpPos - 1),
|
||||
scrollIntoView: true,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {EditorView} view
|
||||
* @param {String} character
|
||||
* @param {number} index the instance of the character
|
||||
* @returns {true}
|
||||
*/
|
||||
export function jumpToCharacter(view, character, index) {
|
||||
const { state, dispatch } = view;
|
||||
const characterPositions = getCharacterPositions(state, character);
|
||||
const pos = characterPositions.at(index) ?? characterPositions.at(-1);
|
||||
if (pos == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const selection = EditorSelection.cursor(pos - 1);
|
||||
dispatch({
|
||||
selection,
|
||||
effects: EditorView.scrollIntoView(
|
||||
selection.head,
|
||||
{ y: "start" }
|
||||
)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {EditorView} view
|
||||
* @param {String} character
|
||||
* @returns {true}
|
||||
*/
|
||||
export function deleteAllInlineBeforeCharacter(view, character) {
|
||||
const { state, dispatch } = view;
|
||||
const characterPositions = getCharacterPositions(state, character);
|
||||
|
||||
const changes = [];
|
||||
characterPositions.forEach((pos) => {
|
||||
const line = state.doc.lineAt(pos);
|
||||
if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) {
|
||||
return;
|
||||
}
|
||||
changes.push({
|
||||
from: line.from,
|
||||
to: pos - 1,
|
||||
insert: '',
|
||||
});
|
||||
});
|
||||
dispatch({ changes });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {EditorView} view
|
||||
* @param {String} character
|
||||
* @param {String} character2
|
||||
* @param {number} index
|
||||
* @returns {true}
|
||||
*/
|
||||
export function InsertCharBeforeChar(view, character, character2, index) {
|
||||
const { state, dispatch } = view;
|
||||
|
||||
const changes = [];
|
||||
const characterPositions = getCharacterPositions(state, character);
|
||||
const labelpos = characterPositions.at(index) ?? characterPositions.at(-1);
|
||||
const line = state.doc.lineAt(labelpos);
|
||||
|
||||
//delete preceeding characters
|
||||
changes.push({
|
||||
from: line.from,
|
||||
to: labelpos - 1,
|
||||
insert: '',
|
||||
});
|
||||
|
||||
changes.push({
|
||||
insert: character2,
|
||||
from: line.from,
|
||||
});
|
||||
|
||||
dispatch({ changes });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
])
|
||||
];
|
||||
+13
-10
@@ -1893,6 +1893,19 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback']
|
||||
*/
|
||||
export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', 'delayfb', 'dfb');
|
||||
|
||||
/**
|
||||
* Sets the level of the signal that is fed back into the delay.
|
||||
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
|
||||
*
|
||||
* @name delayfeedback
|
||||
* @tags orbit, superdough, supradough
|
||||
* @param {number | Pattern} feedback between 0 and 1
|
||||
* @synonyms delayfb, dfb
|
||||
* @example
|
||||
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>")
|
||||
*
|
||||
*/
|
||||
export const { delayspeed } = registerControl('delayspeed');
|
||||
/**
|
||||
* Sets the time of the delay effect.
|
||||
*
|
||||
@@ -1904,16 +1917,6 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback',
|
||||
* note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>")
|
||||
*
|
||||
*/
|
||||
export const { delayspeed } = registerControl('delayspeed');
|
||||
|
||||
/*
|
||||
* @name delaytime
|
||||
* @tags orbit, superdough, supradough
|
||||
* @param {number | Pattern} delaytime sets the time of the delay effect.
|
||||
* @synonyms delayt, dt
|
||||
* @example
|
||||
* note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>")
|
||||
*/
|
||||
export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt');
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,10 +37,9 @@ function humanFileSize(bytes, si) {
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
|
||||
function getSampleInfo(hapValue, bank) {
|
||||
export function getSampleInfo(hapValue, bank) {
|
||||
const { speed = 1.0 } = hapValue;
|
||||
const { transpose, url, index, midi, label, baseFrequency } = getCommonSampleInfo(hapValue, bank);
|
||||
|
||||
const { transpose, url, index, midi, label } = getCommonSampleInfo(hapValue, bank);
|
||||
const playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12);
|
||||
return { transpose, url, index, midi, label, playbackRate };
|
||||
}
|
||||
@@ -169,15 +168,15 @@ export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '')
|
||||
* @param {string} v
|
||||
* @returns {SampleMetaData}
|
||||
*/
|
||||
const fullUrl = (v) => ({ url: baseUrl + v, midi: extractMidiNoteFromString(v) });
|
||||
const getMetaData = (v) => ({ url: baseUrl + v, midi: extractMidiNoteFromString(v) });
|
||||
if (Array.isArray(value)) {
|
||||
//return [key, value.map(replaceUrl)];
|
||||
value = value.map(fullUrl);
|
||||
value = value.map(getMetaData);
|
||||
} else {
|
||||
// must be object
|
||||
value = Object.fromEntries(
|
||||
Object.entries(value).map(([note, samples]) => {
|
||||
return [note, (typeof samples === 'string' ? [samples] : samples).map(fullUrl)];
|
||||
return [note, (typeof samples === 'string' ? [samples] : samples).map(getMetaData)];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
userPattern,
|
||||
} from '../../../user_pattern_utils.mjs';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { getMetadata } from '../../../metadata_parser.js';
|
||||
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
||||
import { parseJSON, isUdels } from '../../util.mjs';
|
||||
import { useSettings } from '../../../settings.mjs';
|
||||
import { ActionButton } from '../button/action-button.jsx';
|
||||
import { Pagination } from '../pagination/Pagination.jsx';
|
||||
@@ -18,13 +20,8 @@ import { useDebounce } from '../usedebounce.jsx';
|
||||
import cx from '@src/cx.mjs';
|
||||
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'> } */) {
|
||||
const meta = pattern.meta
|
||||
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
||||
|
||||
let title = meta.title;
|
||||
if (title == null) {
|
||||
@@ -61,10 +58,6 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
|
||||
return (
|
||||
<div className="p-2">
|
||||
{Object.values(patterns)
|
||||
.sort((a, b) => {
|
||||
return (b.meta.title ?? "").localeCompare(a.meta.title ?? "")
|
||||
|
||||
})
|
||||
.reverse()
|
||||
.map((pattern) => {
|
||||
const id = pattern.id;
|
||||
@@ -100,7 +93,7 @@ export function PatternsTab({ context }) {
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(userPatterns).filter(([_key, pattern]) => {
|
||||
const meta = pattern.meta;
|
||||
const meta = getMetadata(pattern.code);
|
||||
|
||||
// Search for specific meta keys
|
||||
const searchLowercaseTrimmed = search.trim().toLowerCase();
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useStore } from '@nanostores/react';
|
||||
import { register } from '@strudel/core';
|
||||
import { isUdels } from './repl/util.mjs';
|
||||
import { computed } from 'nanostores';
|
||||
import { getMetadata } from './metadata_parser';
|
||||
|
||||
export const audioEngineTargets = {
|
||||
webaudio: 'webaudio',
|
||||
@@ -19,11 +18,6 @@ export const soundFilterType = {
|
||||
ALL: 'all',
|
||||
};
|
||||
|
||||
export const PATTERN_SORT = {
|
||||
"NEWEST": "most recent",
|
||||
"A-Z": "A-Z",
|
||||
}
|
||||
|
||||
export const defaultSettings = {
|
||||
activeFooter: 'intro',
|
||||
keybindings: 'codemirror',
|
||||
@@ -77,7 +71,6 @@ export const $settings = computed(settingsMap, (state) => {
|
||||
Object.keys(userPatterns).forEach((key) => {
|
||||
const data = userPatterns[key];
|
||||
data.id = data.id ?? key;
|
||||
data.meta = getMetadata(data.code)
|
||||
userPatterns[key] = data;
|
||||
});
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user