Compare commits

..

2 Commits

Author SHA1 Message Date
Felix Roos 12fac86012 rename 2026-02-15 00:42:21 +01:00
Felix Roos cea6301ffd restore export 2026-02-15 00:40:13 +01:00
6 changed files with 51 additions and 296 deletions
-44
View File
@@ -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
+6 -78
View File
@@ -20,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';
@@ -49,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()]));
@@ -80,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();
@@ -147,75 +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?.()),
+11 -133
View File
@@ -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;
}
+13 -10
View File
@@ -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');
/**
+19 -29
View File
@@ -9,7 +9,7 @@ import {
onceEnded,
releaseAudioNode,
} from './helpers.mjs';
import { errorLogger, logger } from './logger.mjs';
import { logger } from './logger.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer>
const loadCache = {}; // string: Promise<ArrayBuffer>
@@ -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)];
}),
);
}
@@ -228,31 +227,22 @@ export async function fetchSampleMap(url) {
}
url = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`;
}
const base = getBaseURL(url);
if (typeof fetch !== 'function') {
errorLogger(new Error(`fetch is not supported in this environment. Skipping map load for: ${url}`), 'sampler.mjs')
return [{}, base || ''];
// not a browser
return;
}
try {
const res = await fetch(url);
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 base = getBaseURL(url);
if (typeof fetch === 'undefined') {
// skip fetch when in node / testing
return;
}
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];
}
/**
+2 -2
View File
@@ -963,9 +963,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
let url;
let sample = getSound(ir);
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') {
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);
}