Compare commits

...

71 Commits

Author SHA1 Message Date
Aria 0001d9c27b Change name to setDefaultJoin 2026-01-22 14:24:45 -06:00
Aria d79d122f68 Add docstring and example 2026-01-22 14:08:26 -06:00
Aria 6e61f3cdb6 Make default alignment configurable 2026-01-22 13:52:57 -06:00
froos 7f2860eb14 Merge pull request 'crunch up layout' (#1938) from stylez into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1938
2026-01-22 10:28:08 +01:00
Felix Roos 3364b9f05f keep it simple stupid 2026-01-22 10:12:16 +01:00
Felix Roos f9ed71fbf8 fix: import sounds font size 2026-01-22 01:35:19 +01:00
Felix Roos e9ea0bc7cc fix: typo 2026-01-22 01:32:04 +01:00
Felix Roos a87daf3d21 fix: patterns tab performance 2026-01-22 01:31:23 +01:00
Felix Roos 784b9c08ec copmuted settings 2026-01-22 01:16:57 +01:00
Felix Roos 139b5376fc rebrand hide button row to hide footer 2026-01-22 01:16:22 +01:00
Felix Roos a052def314 piping hot (main menu in footer) + border around panel toggle 2026-01-22 00:35:04 +01:00
Felix Roos bcce1fa68a make move sidepanel one layer up, so tabs are at the top and the toggle doesn't float 2026-01-21 23:38:25 +01:00
Felix Roos 9a10cac8e9 fix bottom panel 2026-01-21 23:07:49 +01:00
Felix Roos cf999462b7 fix: export tab placeholder color 2026-01-21 23:02:14 +01:00
Felix Roos 9fc084b291 tweaks: consistent font sizes and row heights, persist selected reference tag, add wrap prop to ButtonGroup 2026-01-21 22:47:15 +01:00
Felix Roos cb46dd7840 make it crunchier: border borders, less padding 2026-01-21 01:31:25 +01:00
froos b684e183a0 Merge pull request 'add muted theme color + tweak pane styles' (#1936) from stylez into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1936
2026-01-21 00:27:43 +01:00
Felix Roos 84e4dde3d7 mini tweaks 2026-01-21 00:15:00 +01:00
Felix Roos 5f8d028d4f cleanup 2026-01-21 00:04:18 +01:00
Felix Roos e3abdd41c0 make panel toggle floating 2026-01-21 00:03:19 +01:00
Felix Roos bce6ffde08 cleanup + tweak bottom panel style 2026-01-20 23:36:09 +01:00
Felix Roos 35a3e32238 no bg on button group 2026-01-20 23:08:31 +01:00
Felix Roos 60b5024f63 remove panel hover + optimize bottom panel 2026-01-20 23:06:04 +01:00
Felix Roos 4f14ff00d5 responsiveness 2026-01-20 22:29:16 +01:00
Felix Roos 3d3130e880 fine tune 2026-01-20 22:19:32 +01:00
Felix Roos 83d542d06a proper strudel icon + toggle menu from top bar 2026-01-20 22:13:03 +01:00
Aria 6105d5027d Merge pull request 'Feat: Add retrig option for LFOs' (#1934) from glossing/lfo-retrig into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1934
2026-01-20 04:37:19 +01:00
Felix Roos 7e34069021 fix more font sizes 2026-01-20 01:06:07 +01:00
Felix Roos b75ca193a2 fix: font sizes 2026-01-20 01:01:20 +01:00
Felix Roos afac1f62ca fix: adapt color-theme attribute on root based on theme 2026-01-20 00:59:43 +01:00
Felix Roos de83805fd9 add muted theme color + use it for inputs and tags 2026-01-20 00:06:44 +01:00
Felix Roos 38b7bcb3d2 style tweaks 2026-01-19 23:00:14 +01:00
Aria 62bf9abed3 Add retrig option for LFOs 2026-01-19 09:52:49 -08:00
froos 8ec1d4d034 Merge pull request 'fix: move kabelsalat web to superdough' (#1932) from fix-kabelsalat-dependency into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1932
2026-01-18 22:27:45 +01:00
Felix Roos 6981638f70 fix: move kabelsalat web to superdough 2026-01-18 22:04:49 +01:00
froos f5823ce981 Merge pull request 'Add tags to reference' (#1645) from vvolhejn/strudel:radical-new-docs into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1645
2026-01-18 12:36:49 +01:00
Václav Volhejn f8750901f0 Tag the remaining functions 2026-01-18 12:16:50 +01:00
Václav Volhejn c1fd8c82c6 Merge branch 'radical-new-docs' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-18 12:08:17 +01:00
Václav Volhejn 6e36bdfff7 Add tags for some of the new functions 2026-01-18 12:08:12 +01:00
Felix Roos 8dbdd0a9b8 Merge branch 'main' into radical-new-docs 2026-01-18 10:39:53 +01:00
froos f4c0281613 Merge pull request 'fix: performance issues when reference is open' (#1926) from robojumper/strudel:fix/reference-performance-issue into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1926
2026-01-18 10:36:00 +01:00
Václav Volhejn 99cf256ee2 Clean up 2 2026-01-17 20:11:26 +01:00
Václav Volhejn 42d0bfadce Clean up 2026-01-17 20:08:23 +01:00
Václav Volhejn 22f78fbb2f Merge branch 'radical-new-docs' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-17 19:24:41 +01:00
Václav Volhejn 77076a1c3e Improve reference search/filtering semantics 2026-01-17 19:12:23 +01:00
Václav Volhejn a1d31df556 Merge branch 'main' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-17 18:22:56 +01:00
robojumper f9d0e154e8 fix: performance issues when reference is open 2026-01-17 17:51:32 +01:00
Felix Roos 9ffd0e496f fix: all doc 2026-01-11 12:33:03 +01:00
Felix Roos 550d7ff6cb fix: react error 2026-01-11 12:32:54 +01:00
Václav Volhejn 726e5ef14a Fix formatting 2026-01-10 07:18:29 -08:00
Václav Volhejn cc15e3cd36 Merge branch 'main' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2026-01-10 06:58:39 -08:00
Václav Volhejn 2fa8ed1424 Revert seq synonym refactor 2025-11-09 20:21:19 +01:00
Václav Volhejn b7827cb89f Tag new functions 2025-11-01 21:11:57 +01:00
Václav Volhejn 29fd541f1f Merge branch 'radical-new-docs' of ssh://codeberg.org/vvolhejn/strudel into radical-new-docs 2025-11-01 20:20:07 +01:00
Václav Volhejn 0830a9df69 Add superdirt tags 2025-11-01 20:19:57 +01:00
froos 4159c8394d Merge branch 'main' into radical-new-docs 2025-10-28 22:02:10 +01:00
Václav Volhejn 466bf819d8 Fix formatting (trailing space) 2025-10-19 21:38:46 +02:00
Václav Volhejn 7e4cbefc8f Tag crossfade as internals 2025-10-19 21:22:57 +02:00
Václav Volhejn 2483c673a4 Effects -> fx, untag "crossfade" 2025-10-19 21:19:42 +02:00
Václav Volhejn 69360b3ec8 Also tag "noise" 2025-10-19 21:17:25 +02:00
Václav Volhejn 96d0f2053e Add supradough controls 2025-10-19 21:13:14 +02:00
Václav Volhejn b952e4c735 Add superdough tags 2025-10-19 20:54:22 +02:00
Václav Volhejn a4212589e5 Recategorize 2025-10-12 16:33:07 +02:00
Václav Volhejn ee1a894f3b Show tags in reference, more compact functions list 2025-10-12 16:28:05 +02:00
Václav Volhejn c62428ca27 Document remaining functions 2025-10-12 16:07:11 +02:00
Václav Volhejn d516d765d5 Redo frontend for tags 2025-10-11 14:12:45 +02:00
Václav Volhejn 5032b4e966 Rename group -> tags 2025-10-11 14:11:50 +02:00
Václav Volhejn a4d7803d43 Annotate controls.mjs 2025-10-02 21:59:45 +02:00
Václav Volhejn 4a395f372d Annote pattern.mjs and signal.mjs 2025-10-01 23:45:48 +02:00
Václav Volhejn c754dab1ef Order groups 2025-10-01 22:31:03 +02:00
Václav Volhejn df54404733 Group by category 2025-10-01 22:21:03 +02:00
103 changed files with 1413 additions and 628 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"devToolbar": {
"enabled": false
}
}
+7
View File
@@ -12,6 +12,13 @@ function defineTags(dictionary) {
doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/); doclet.synonyms = doclet.synonyms_text.split(/[ ,]+/);
}, },
}); });
dictionary.defineTag('tags', {
mustHaveValue: true,
onTagged: function (doclet, tag) {
doclet.tags = tag.value.split(/[ ,]+/);
},
});
} }
module.exports = { defineTags: defineTags }; module.exports = { defineTags: defineTags };
+1
View File
@@ -434,6 +434,7 @@ function s4() {
/** /**
* Overrides the css of highlighted events. Make sure to use single quotes! * Overrides the css of highlighted events. Make sure to use single quotes!
* @name markcss * @name markcss
* @tag visualization
* @example * @example
* note("c a f e") * note("c a f e")
* .markcss('text-decoration:underline') * .markcss('text-decoration:underline')
+1
View File
@@ -117,6 +117,7 @@ export const sliderPlugin = ViewPlugin.fromClass(
* Displays a slider widget to allow the user manipulate a value * Displays a slider widget to allow the user manipulate a value
* *
* @name slider * @name slider
* @tags external_io, visualization
* @param {number} value Initial value * @param {number} value Initial value
* @param {number} min Minimum value - optional, defaults to 0 * @param {number} min Minimum value - optional, defaults to 0
* @param {number} max Maximum value - optional, defaults to 1 * @param {number} max Maximum value - optional, defaults to 1
+1
View File
@@ -198,6 +198,7 @@ export function activateTheme(name) {
const themeSettings = settings[name] || settings.strudelTheme; const themeSettings = settings[name] || settings.strudelTheme;
// set css variables // set css variables
themeStyle.innerHTML = `:root { themeStyle.innerHTML = `:root {
color-scheme: ${themeSettings.light ? 'light' : 'dark'};
${Object.entries(themeSettings) ${Object.entries(themeSettings)
// important to override fallback // important to override fallback
.map(([key, value]) => `--${key}: ${value} !important;`) .map(([key, value]) => `--${key}: ${value} !important;`)
+3 -1
View File
@@ -17,13 +17,15 @@ export const settings = {
background: 'white', background: 'white',
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: deepPurple, foreground: deepPurple,
muted: deepPurple + '50',
caret: '#797977', caret: '#797977',
selection: yellowPink, selection: yellowPink,
selectionMatch: '#2B323D', selectionMatch: '#2B323D',
gutterBackground: grey, gutterBackground: pinkAccent,
gutterForeground: lightGrey, gutterForeground: lightGrey,
gutterBorder: 'transparent', gutterBorder: 'transparent',
lineHighlight: pinkAccent, lineHighlight: pinkAccent,
light: true,
}; };
export default createTheme({ export default createTheme({
+1
View File
@@ -31,6 +31,7 @@ const palette = palettes['Sour Watermelon B'];
export const settings = { export const settings = {
background: palette[3], background: palette[3],
foreground: palette[1], foreground: palette[1],
muted: palette[1] + '50',
caret: palette[0], caret: palette[0],
selection: palette[0], selection: palette[0],
selectionMatch: palette[1], selectionMatch: palette[1],
+2
View File
@@ -8,6 +8,7 @@ export const settings = {
background: '#282b2e', background: '#282b2e',
lineBackground: '#282b2e99', lineBackground: '#282b2e99',
foreground: '#a9b7c6', foreground: '#a9b7c6',
muted: '#a9b7c650',
caret: '#00FF00', caret: '#00FF00',
selection: '#343739', selection: '#343739',
selectionMatch: '#343739', selectionMatch: '#343739',
@@ -19,6 +20,7 @@ export default createTheme({
settings: { settings: {
background: '#282b2e', background: '#282b2e',
foreground: '#a9b7c6', foreground: '#a9b7c6',
muted: '#a9b7c650',
caret: '#00FF00', caret: '#00FF00',
selection: '#4e5254', selection: '#4e5254',
selectionMatch: '#4e5254', selectionMatch: '#4e5254',
+1
View File
@@ -12,6 +12,7 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[1], foreground: hex[1],
muted: hex[2],
selection: hex[2], selection: hex[2],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
+2
View File
@@ -11,6 +11,7 @@ export const settings = {
background: '#272C35', background: '#272C35',
lineBackground: '#272C3599', lineBackground: '#272C3599',
foreground: 'hsl(220, 14%, 71%)', foreground: 'hsl(220, 14%, 71%)',
muted: 'hsl(220, 14%, 41%)',
caret: '#797977', caret: '#797977',
selection: '#ffffff30', selection: '#ffffff30',
selectionMatch: '#2B323D', selectionMatch: '#2B323D',
@@ -25,6 +26,7 @@ export default createTheme({
settings: { settings: {
background: '#272C35', background: '#272C35',
foreground: '#9d9b97', foreground: '#9d9b97',
muted: 'hsl(220, 14%, 41%)',
caret: '#797977', caret: '#797977',
selection: '#3d4c64', selection: '#3d4c64',
selectionMatch: '#3d4c64', selectionMatch: '#3d4c64',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#21202e', background: '#21202e',
lineBackground: '#21202e99', lineBackground: '#21202e99',
foreground: '#edecee', foreground: '#edecee',
muted: '#edecee50',
caret: '#a277ff', caret: '#a277ff',
selection: '#3d375e7f', selection: '#3d375e7f',
selectionMatch: '#3d375e7f', selectionMatch: '#3d375e7f',
@@ -18,6 +19,7 @@ export default createTheme({
settings: { settings: {
background: '#21202e', background: '#21202e',
foreground: '#edecee', foreground: '#edecee',
muted: '#edecee50',
caret: '#a277ff', caret: '#a277ff',
selection: '#5a51898f', selection: '#5a51898f',
selectionMatch: '#5a51898f', selectionMatch: '#5a51898f',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#FFFFFF', background: '#FFFFFF',
lineBackground: '#FFFFFF99', lineBackground: '#FFFFFF99',
foreground: '#000000', foreground: '#000000',
muted: '#00000050',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#FFD420', selection: '#FFD420',
selectionMatch: '#FFD420', selectionMatch: '#FFD420',
@@ -20,6 +21,7 @@ export default createTheme({
settings: { settings: {
background: '#FFFFFF', background: '#FFFFFF',
foreground: '#000000', foreground: '#000000',
muted: '#00000050',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#FFD420', selection: '#FFD420',
selectionMatch: '#FFD420', selectionMatch: '#FFD420',
+2 -1
View File
@@ -2,7 +2,8 @@ import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: 'black', background: 'black',
foreground: 'white', // whats that? foreground: 'white',
muted: '#ffffff50',
caret: 'white', caret: 'white',
selection: '#ffffff20', selection: '#ffffff20',
selectionMatch: '#036dd626', selectionMatch: '#036dd626',
+2 -1
View File
@@ -3,7 +3,8 @@ import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: '#051DB5', background: '#051DB5',
lineBackground: '#051DB550', lineBackground: '#051DB550',
foreground: 'white', // whats that? foreground: 'white',
muted: '#ffffff50',
caret: 'white', caret: 'white',
selection: 'rgba(128, 203, 196, 0.5)', selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626', selectionMatch: '#036dd626',
+1
View File
@@ -11,6 +11,7 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[2], foreground: hex[2],
muted: hex[3],
selection: hex[3], selection: hex[3],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
+2
View File
@@ -9,6 +9,7 @@ export const settings = {
background: '#242424', background: '#242424',
lineBackground: '#24242499', lineBackground: '#24242499',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
@@ -23,6 +24,7 @@ export default createTheme({
settings: { settings: {
background: '#242424', background: '#242424',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
+2
View File
@@ -11,6 +11,7 @@ export const settings = {
background: '#282a36', background: '#282a36',
lineBackground: '#282a3699', lineBackground: '#282a3699',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#f8f8f0', caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
@@ -27,6 +28,7 @@ export default createTheme({
settings: { settings: {
background: '#282a36', background: '#282a36',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#f8f8f0', caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
+2
View File
@@ -10,6 +10,7 @@ export const settings = {
background: '#2a2734', background: '#2a2734',
lineBackground: '#2a273499', lineBackground: '#2a273499',
foreground: '#eeebff', foreground: '#eeebff',
muted: '#eeebff50',
caret: '#ffad5c', caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734', gutterBackground: '#2a2734',
@@ -22,6 +23,7 @@ export default createTheme({
settings: { settings: {
background: '#2a2734', background: '#2a2734',
foreground: '#6c6783', foreground: '#6c6783',
muted: '#eeebff50',
caret: '#ffad5c', caret: '#ffad5c',
selection: '#9a86fd', selection: '#9a86fd',
selectionMatch: '#9a86fd', selectionMatch: '#9a86fd',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#fff', background: '#fff',
lineBackground: '#ffffff99', lineBackground: '#ffffff99',
foreground: '#000', foreground: '#000',
muted: '#00000050',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#d7d4f0', selection: '#d7d4f0',
selectionMatch: '#d7d4f0', selectionMatch: '#d7d4f0',
@@ -20,6 +21,7 @@ export default createTheme({
settings: { settings: {
background: '#fff', background: '#fff',
foreground: '#000', foreground: '#000',
muted: '#00000050',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#d7d4f0', selection: '#d7d4f0',
selectionMatch: '#d7d4f0', selectionMatch: '#d7d4f0',
+1
View File
@@ -23,6 +23,7 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[10], foreground: hex[10],
muted: hex[7],
selection: hex[8], selection: hex[8],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[3], gutterBackground: hex[3],
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#0d1117', background: '#0d1117',
lineBackground: '#0d111799', lineBackground: '#0d111799',
foreground: '#c9d1d9', foreground: '#c9d1d9',
muted: '#c9d1d950',
caret: '#c9d1d9', caret: '#c9d1d9',
selection: '#003d73', selection: '#003d73',
selectionMatch: '#003d73', selectionMatch: '#003d73',
@@ -16,6 +17,7 @@ export default createTheme({
settings: { settings: {
background: '#0d1117', background: '#0d1117',
foreground: '#c9d1d9', foreground: '#c9d1d9',
muted: '#c9d1d950',
caret: '#c9d1d9', caret: '#c9d1d9',
selection: '#003d73', selection: '#003d73',
selectionMatch: '#003d73', selectionMatch: '#003d73',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#fff', background: '#fff',
lineBackground: '#ffffff99', lineBackground: '#ffffff99',
foreground: '#24292e', foreground: '#24292e',
muted: '#24292e50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
@@ -17,6 +18,7 @@ export default createTheme({
settings: { settings: {
background: '#fff', background: '#fff',
foreground: '#24292e', foreground: '#24292e',
muted: '#24292e50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
+1
View File
@@ -13,6 +13,7 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[2], foreground: hex[2],
muted: hex[2] + '50',
selection: hex[4], selection: hex[4],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
+2
View File
@@ -10,6 +10,7 @@ export const settings = {
background: '#282828', background: '#282828',
lineBackground: '#28282899', lineBackground: '#28282899',
foreground: '#ebdbb2', foreground: '#ebdbb2',
muted: '#ebdbb250',
caret: '#ebdbb2', caret: '#ebdbb2',
selection: '#bdae93', selection: '#bdae93',
selectionMatch: '#bdae93', selectionMatch: '#bdae93',
@@ -23,6 +24,7 @@ export default createTheme({
settings: { settings: {
background: '#282828', background: '#282828',
foreground: '#ebdbb2', foreground: '#ebdbb2',
muted: '#ebdbb250',
caret: '#ebdbb2', caret: '#ebdbb2',
selection: '#b99d555c', selection: '#b99d555c',
selectionMatch: '#b99d555c', selectionMatch: '#b99d555c',
+2
View File
@@ -11,6 +11,7 @@ export const settings = {
background: '#fbf1c7', background: '#fbf1c7',
lineBackground: '#fbf1c799', lineBackground: '#fbf1c799',
foreground: '#3c3836', foreground: '#3c3836',
muted: '#3c383650',
caret: '#af3a03', caret: '#af3a03',
selection: '#ebdbb2', selection: '#ebdbb2',
selectionMatch: '#bdae93', selectionMatch: '#bdae93',
@@ -25,6 +26,7 @@ export default createTheme({
settings: { settings: {
background: '#fbf1c7', background: '#fbf1c7',
foreground: '#3c3836', foreground: '#3c3836',
muted: '#3c383650',
caret: '#af3a03', caret: '#af3a03',
selection: '#bdae9391', selection: '#bdae9391',
selectionMatch: '#bdae9391', selectionMatch: '#bdae9391',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#212121', background: '#212121',
lineBackground: '#21212199', lineBackground: '#21212199',
foreground: '#bdbdbd', foreground: '#bdbdbd',
muted: '#bdbdbd50',
caret: '#a0a4ae', caret: '#a0a4ae',
selection: '#d7d4f0', selection: '#d7d4f0',
selectionMatch: '#d7d4f0', selectionMatch: '#d7d4f0',
@@ -19,6 +20,7 @@ export default createTheme({
settings: { settings: {
background: '#212121', background: '#212121',
foreground: '#bdbdbd', foreground: '#bdbdbd',
muted: '#bdbdbd50',
caret: '#a0a4ae', caret: '#a0a4ae',
selection: '#d7d4f063', selection: '#d7d4f063',
selectionMatch: '#d7d4f063', selectionMatch: '#d7d4f063',
+3 -1
View File
@@ -5,7 +5,8 @@ export const settings = {
light: true, light: true,
background: '#FAFAFA', background: '#FAFAFA',
lineBackground: '#FAFAFA99', lineBackground: '#FAFAFA99',
foreground: '#90A4AE', foreground: '#6182B8',
muted: '#6182B850',
caret: '#272727', caret: '#272727',
selection: '#80CBC440', selection: '#80CBC440',
selectionMatch: '#FAFAFA', selectionMatch: '#FAFAFA',
@@ -19,6 +20,7 @@ export default createTheme({
settings: { settings: {
background: '#FAFAFA', background: '#FAFAFA',
foreground: '#90A4AE', foreground: '#90A4AE',
muted: '#6182B850',
caret: '#272727', caret: '#272727',
selection: '#80CBC440', selection: '#80CBC440',
selectionMatch: '#80CBC440', selectionMatch: '#80CBC440',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#272822', background: '#272822',
lineBackground: '#27282299', lineBackground: '#27282299',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#49483E', selection: '#49483E',
selectionMatch: '#49483E', selectionMatch: '#49483E',
@@ -18,6 +19,7 @@ export default createTheme({
settings: { settings: {
background: '#272822', background: '#272822',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#49483E', selection: '#49483E',
selectionMatch: '#49483E', selectionMatch: '#49483E',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#f2f1f8', background: '#f2f1f8',
lineBackground: '#f2f1f899', lineBackground: '#f2f1f899',
foreground: '#0c006b', foreground: '#0c006b',
muted: '#0c006b50',
caret: '#5c49e9', caret: '#5c49e9',
selection: '#d5d1f2', selection: '#d5d1f2',
selectionMatch: '#d5d1f2', selectionMatch: '#d5d1f2',
@@ -19,6 +20,7 @@ export default createTheme({
settings: { settings: {
background: '#f2f1f8', background: '#f2f1f8',
foreground: '#0c006b', foreground: '#0c006b',
muted: '#0c006b50',
caret: '#5c49e9', caret: '#5c49e9',
selection: '#d5d1f2', selection: '#d5d1f2',
selectionMatch: '#d5d1f2', selectionMatch: '#d5d1f2',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#2e3440', background: '#2e3440',
lineBackground: '#2e344099', lineBackground: '#2e344099',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#3b4252', selection: '#3b4252',
selectionMatch: '#e5e9f0', selectionMatch: '#e5e9f0',
@@ -20,6 +21,7 @@ export default createTheme({
settings: { settings: {
background: '#2e3440', background: '#2e3440',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#00000073', selection: '#00000073',
selectionMatch: '#00000073', selectionMatch: '#00000073',
+1
View File
@@ -13,6 +13,7 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[2], foreground: hex[2],
muted: hex[2] + '50',
selection: hex[4], selection: hex[4],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#002b36', background: '#002b36',
lineBackground: '#002b3699', lineBackground: '#002b3699',
foreground: '#93a1a1', foreground: '#93a1a1',
muted: '#93a1a150',
caret: '#839496', caret: '#839496',
selection: '#173541', selection: '#173541',
selectionMatch: '#aafe661a', selectionMatch: '#aafe661a',
@@ -45,6 +46,7 @@ export default createTheme({
settings: { settings: {
background: c.background, background: c.background,
foreground: c.foreground, foreground: c.foreground,
muted: c.muted,
caret: c.cursor, caret: c.cursor,
selection: c.selection, selection: c.selection,
selectionMatch: c.selection, selectionMatch: c.selection,
+2
View File
@@ -8,6 +8,7 @@ export const settings = {
background: '#fdf6e3', background: '#fdf6e3',
lineBackground: '#fdf6e399', lineBackground: '#fdf6e399',
foreground: '#657b83', foreground: '#657b83',
muted: '#657b8350',
caret: '#586e75', caret: '#586e75',
selection: '#dfd9c8', selection: '#dfd9c8',
selectionMatch: '#dfd9c8', selectionMatch: '#dfd9c8',
@@ -19,6 +20,7 @@ export const settings = {
const c = { const c = {
background: '#FDF6E3', background: '#FDF6E3',
foreground: '#657B83', foreground: '#657B83',
muted: '#657b8350',
selection: '#EEE8D5', selection: '#EEE8D5',
selectionMatch: '#EEE8D5', selectionMatch: '#EEE8D5',
cursor: '#657B83', cursor: '#657B83',
+1
View File
@@ -13,6 +13,7 @@ export const settings = {
background: '#000000', background: '#000000',
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[4], foreground: hex[4],
muted: hex[6],
selection: hex[6], selection: hex[6],
gutterBackground: hex[0], gutterBackground: hex[0],
gutterForeground: hex[5], gutterForeground: hex[5],
+1
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#222', background: '#222',
lineBackground: '#22222299', lineBackground: '#22222299',
foreground: '#fff', foreground: '#fff',
muted: '#8a919966',
caret: '#ffcc00', caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)', selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626', selectionMatch: '#036dd626',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#303841', background: '#303841',
lineBackground: '#30384199', lineBackground: '#30384199',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#4C5964', selection: '#4C5964',
selectionMatch: '#3A546E', selectionMatch: '#3A546E',
@@ -18,6 +19,7 @@ export default createTheme({
settings: { settings: {
background: '#303841', background: '#303841',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#4C5964', selection: '#4C5964',
selectionMatch: '#3A546E', selectionMatch: '#3A546E',
+2 -1
View File
@@ -9,7 +9,8 @@ let colorD = '#f8fc55';
export const settings = { export const settings = {
background: '#000000', background: '#000000',
foreground: colorA, // whats that? foreground: colorA,
muted: colorA + '50',
caret: colorC, caret: colorC,
selection: colorD, selection: colorD,
selectionMatch: colorA, selectionMatch: colorA,
+4 -2
View File
@@ -4,7 +4,8 @@ import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: '#24283b', background: '#24283b',
lineBackground: '#24283b99', lineBackground: '#24283b99',
foreground: '#7982a9', foreground: '#f9f2f9',
muted: '#f9f2f950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#6f7bb630', selection: '#6f7bb630',
selectionMatch: '#1f2335', selectionMatch: '#1f2335',
@@ -18,7 +19,8 @@ export default createTheme({
theme: 'dark', theme: 'dark',
settings: { settings: {
background: '#24283b', background: '#24283b',
foreground: '#7982a9', foreground: '#f9f2f9',
muted: '#f9f2f950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#6f7bb630', selection: '#6f7bb630',
selectionMatch: '#343b5f', selectionMatch: '#343b5f',
+4 -2
View File
@@ -4,7 +4,8 @@ import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: '#1a1b26', background: '#1a1b26',
lineBackground: '#1a1b2699', lineBackground: '#1a1b2699',
foreground: '#787c99', foreground: '#f8fcf9',
muted: '#f8fcf950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#515c7e40', selection: '#515c7e40',
selectionMatch: '#16161e', selectionMatch: '#16161e',
@@ -18,7 +19,8 @@ export default createTheme({
theme: 'dark', theme: 'dark',
settings: { settings: {
background: '#1a1b26', background: '#1a1b26',
foreground: '#787c99', foreground: '#f8fcf9',
muted: '#f8fcf950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#515c7e40', selection: '#515c7e40',
selectionMatch: '#16161e', selectionMatch: '#16161e',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#e1e2e7', background: '#e1e2e7',
lineBackground: '#e1e2e799', lineBackground: '#e1e2e799',
foreground: '#3760bf', foreground: '#3760bf',
muted: '#3760bf50',
caret: '#3760bf', caret: '#3760bf',
selection: '#99a7df', selection: '#99a7df',
selectionMatch: '#99a7df', selectionMatch: '#99a7df',
@@ -20,6 +21,7 @@ export default createTheme({
settings: { settings: {
background: '#e1e2e7', background: '#e1e2e7',
foreground: '#3760bf', foreground: '#3760bf',
muted: '#3760bf50',
caret: '#3760bf', caret: '#3760bf',
selection: '#99a7df', selection: '#99a7df',
selectionMatch: '#99a7df', selectionMatch: '#99a7df',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#1e1e1e', background: '#1e1e1e',
lineBackground: '#1e1e1e99', lineBackground: '#1e1e1e99',
foreground: '#fff', foreground: '#fff',
muted: '#ffffff50',
caret: '#c6c6c6', caret: '#c6c6c6',
selection: '#6199ff2f', selection: '#6199ff2f',
selectionMatch: '#72a1ff59', selectionMatch: '#72a1ff59',
@@ -19,6 +20,7 @@ export default createTheme({
settings: { settings: {
background: '#1e1e1e', background: '#1e1e1e',
foreground: '#fff', foreground: '#fff',
muted: '#ffffff50',
caret: '#c6c6c6', caret: '#c6c6c6',
selection: '#6199ff2f', selection: '#6199ff2f',
selectionMatch: '#72a1ff59', selectionMatch: '#72a1ff59',
+2
View File
@@ -5,6 +5,7 @@ export const settings = {
background: '#ffffff', background: '#ffffff',
lineBackground: '#ffffff50', lineBackground: '#ffffff50',
foreground: '#383a42', foreground: '#383a42',
muted: '#383a4250',
caret: '#000', caret: '#000',
selection: '#add6ff', selection: '#add6ff',
selectionMatch: '#a8ac94', selectionMatch: '#a8ac94',
@@ -20,6 +21,7 @@ export default createTheme({
settings: { settings: {
background: '#ffffff', background: '#ffffff',
foreground: '#383a42', foreground: '#383a42',
muted: '#383a4250',
caret: '#000', caret: '#000',
selection: '#add6ff', selection: '#add6ff',
selectionMatch: '#a8ac94', selectionMatch: '#a8ac94',
+2 -1
View File
@@ -2,7 +2,8 @@ import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: 'white', background: 'white',
foreground: 'black', // whats that? foreground: 'black',
muted: '#00000050',
caret: 'black', caret: 'black',
selection: 'rgba(128, 203, 196, 0.5)', selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#ffffff26', selectionMatch: '#ffffff26',
+2
View File
@@ -6,6 +6,7 @@ export const settings = {
background: '#fff', background: '#fff',
lineBackground: '#ffffff99', lineBackground: '#ffffff99',
foreground: '#3D3D3D', foreground: '#3D3D3D',
muted: '#3D3D3D50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
@@ -18,6 +19,7 @@ export default createTheme({
settings: { settings: {
background: '#fff', background: '#fff',
foreground: '#3D3D3D', foreground: '#3D3D3D',
muted: '#3D3D3D50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
File diff suppressed because it is too large Load Diff
+1
View File
@@ -15,6 +15,7 @@ import Fraction, { gcd } from './fraction.mjs';
* - "-" hold previous value * - "-" hold previous value
* - "." silence * - "." silence
* *
* @tags visualization
* @param {Pattern} pattern the pattern to use * @param {Pattern} pattern the pattern to use
* @param {number} chars max number of characters (approximately) * @param {number} chars max number of characters (approximately)
* @returns string * @returns string
+5
View File
@@ -61,6 +61,7 @@ export const bjorklund = function (ons, steps) {
* *
* @memberof Pattern * @memberof Pattern
* @name euclid * @name euclid
* @tags temporal
* @param {number} pulses the number of onsets/beats * @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill * @param {number} steps the number of steps to fill
* @returns Pattern * @returns Pattern
@@ -73,6 +74,7 @@ export const bjorklund = function (ons, steps) {
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence. * Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
* @memberof Pattern * @memberof Pattern
* @name euclidRot * @name euclidRot
* @tags temporal
* @param {number} pulses the number of onsets/beats * @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill * @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps * @param {number} rotation offset in steps
@@ -156,6 +158,7 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
* so there will be no gaps. * so there will be no gaps.
* @name euclidLegato * @name euclidLegato
* @memberof Pattern * @memberof Pattern
* @tags temporal
* @param {number} pulses the number of onsets/beats * @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill * @param {number} steps the number of steps to fill
* @param rotation offset in steps * @param rotation offset in steps
@@ -187,6 +190,7 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
* the resulting sequence * the resulting sequence
* @name euclidLegatoRot * @name euclidLegatoRot
* @memberof Pattern * @memberof Pattern
* @tags temporal
* @param {number} pulses the number of onsets/beats * @param {number} pulses the number of onsets/beats
* @param {number} steps the number of steps to fill * @param {number} steps the number of steps to fill
* @param {number} rotation offset in steps * @param {number} rotation offset in steps
@@ -208,6 +212,7 @@ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, s
* @name euclidish * @name euclidish
* @synonyms eish * @synonyms eish
* @memberof Pattern * @memberof Pattern
* @tags temporal
* @param {number} pulses the number of onsets * @param {number} pulses the number of onsets
* @param {number} steps the number of steps to fill * @param {number} steps the number of steps to fill
* @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse) * @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse)
-1
View File
@@ -31,7 +31,6 @@
}, },
"homepage": "https://strudel.cc", "homepage": "https://strudel.cc",
"dependencies": { "dependencies": {
"@kabelsalat/web": "^0.4.1",
"fraction.js": "^5.2.1" "fraction.js": "^5.2.1"
}, },
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2", "gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
+282 -92
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -28,6 +28,7 @@ const _pick = function (lookup, pat, modulo = true) {
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `inhabit`, but maintains the structure of the original patterns. * Similar to `inhabit`, but maintains the structure of the original patterns.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -57,6 +58,7 @@ const __pick = register('pick', function (lookup, pat) {
* it wraps around, rather than sticking at the maximum value. * it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the * For example, if you pick the fifth pattern of a list of three, you'll get the
* second one. * second one.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -67,6 +69,7 @@ export const pickmod = register('pickmod', function (lookup, pat) {
}); });
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern. /** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
* @tags combiners, functional
* @param {Pattern} pat * @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices * @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull * @param {function[]} funcs the array of functions from which to pull
@@ -83,6 +86,7 @@ export const pickF = register('pickF', function (lookup, funcs, pat) {
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list, /** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
* it wraps around, rather than sticking at the maximum value. * it wraps around, rather than sticking at the maximum value.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices * @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull * @param {function[]} funcs the array of functions from which to pull
@@ -93,6 +97,7 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
}); });
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin. /** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -103,6 +108,7 @@ export const pickOut = register('pickOut', function (lookup, pat) {
/** * The same as `pickOut`, but if you pick a number greater than the size of the list, /** * The same as `pickOut`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value. * it wraps around, rather than sticking at the maximum value.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -112,6 +118,7 @@ export const pickmodOut = register('pickmodOut', function (lookup, pat) {
}); });
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered. /** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -122,6 +129,7 @@ export const pickRestart = register('pickRestart', function (lookup, pat) {
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list, /** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value. * it wraps around, rather than sticking at the maximum value.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -138,6 +146,7 @@ export const pickmodRestart = register('pickmodRestart', function (lookup, pat)
}); });
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered. /** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -148,6 +157,7 @@ export const pickReset = register('pickReset', function (lookup, pat) {
/** * The same as `pickReset`, but if you pick a number greater than the size of the list, /** * The same as `pickReset`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value. * it wraps around, rather than sticking at the maximum value.
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -159,6 +169,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
/** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). /** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. * Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
* @name inhabit * @name inhabit
* @tags combiners
* @synonyms pickSqueeze * @synonyms pickSqueeze
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
@@ -180,6 +191,7 @@ export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], fun
* second one. * second one.
* @name inhabitmod * @name inhabitmod
* @synonyms pickmodSqueeze * @synonyms pickmodSqueeze
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -192,6 +204,7 @@ export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSq
/** /**
* Pick from the list of values (or patterns of values) via the index using the given * Pick from the list of values (or patterns of values) via the index using the given
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event * pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
* @tags combiners
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
+8 -10
View File
@@ -12,7 +12,6 @@ import {
import { evalScope } from './evaluate.mjs'; import { evalScope } from './evaluate.mjs';
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs'; import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
import { reset_state } from './impure.mjs'; import { reset_state } from './impure.mjs';
import { SalatRepl } from '@kabelsalat/web';
export function repl({ export function repl({
defaultOutput, defaultOutput,
@@ -31,7 +30,6 @@ export function repl({
id, id,
mondo = false, mondo = false,
}) { }) {
const kabel = new SalatRepl({ localScope: true });
const state = { const state = {
schedulerError: undefined, schedulerError: undefined,
evalError: undefined, evalError: undefined,
@@ -89,11 +87,6 @@ export function repl({
return silence; return silence;
}; };
const compileKabel = (code) => {
const node = kabel.evaluate(code);
return node.compile({ log: false });
};
// helper to get a patternified pure value out // helper to get a patternified pure value out
function unpure(pat) { function unpure(pat) {
if (pat._Pattern) { if (pat._Pattern) {
@@ -123,6 +116,7 @@ export function repl({
* Changes the global tempo to the given cycles per minute * Changes the global tempo to the given cycles per minute
* *
* @name setcpm * @name setcpm
* @tags temporal
* @alias setCpm * @alias setCpm
* @param {number} cpm cycles per minute * @param {number} cpm cycles per minute
* @example * @example
@@ -136,7 +130,9 @@ export function repl({
// TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`.. // TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`..
/** Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for let allTransforms = [];
/**
* Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for
* a version that applies the function to each pattern separately. * a version that applies the function to each pattern separately.
* ``` * ```
* $: sound("bd - cp sd") * $: sound("bd - cp sd")
@@ -148,18 +144,21 @@ export function repl({
* $: sound("hh*8") * $: sound("hh*8")
* all(x => x.pianoroll()) * all(x => x.pianoroll())
* ``` * ```
*
* @tags combiners
*/ */
let allTransforms = [];
const all = function (transform) { const all = function (transform) {
allTransforms.push(transform); allTransforms.push(transform);
return silence; return silence;
}; };
/** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern. /** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern.
*
* ``` * ```
* $: sound("bd - cp sd") * $: sound("bd - cp sd")
* $: sound("hh*8") * $: sound("hh*8")
* each(fast("<2 3>")) * each(fast("<2 3>"))
* ``` * ```
* @tags combiners
*/ */
const each = function (transform) { const each = function (transform) {
eachTransform = transform; eachTransform = transform;
@@ -215,7 +214,6 @@ export function repl({
setcps: setCps, setcps: setCps,
setCpm, setCpm,
setcpm: setCpm, setcpm: setCpm,
compileKabel,
}); });
}; };
+63
View File
@@ -24,6 +24,7 @@ export const signal = (func) => {
* A sawtooth signal between 0 and 1. * A sawtooth signal between 0 and 1.
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
* @example * @example
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8") * note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
* .clip(saw.slow(2)) * .clip(saw.slow(2))
@@ -38,6 +39,7 @@ export const saw = signal((t) => t % 1);
* A sawtooth signal between -1 and 1 (like `saw`, but bipolar). * A sawtooth signal between -1 and 1 (like `saw`, but bipolar).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const saw2 = saw.toBipolar(); export const saw2 = saw.toBipolar();
@@ -45,6 +47,7 @@ export const saw2 = saw.toBipolar();
* A sawtooth signal between 1 and 0 (like `saw`, but flipped). * A sawtooth signal between 1 and 0 (like `saw`, but flipped).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
* @example * @example
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8") * note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
* .clip(isaw.slow(2)) * .clip(isaw.slow(2))
@@ -59,6 +62,7 @@ export const isaw = signal((t) => 1 - (t % 1));
* A sawtooth signal between 1 and -1 (like `saw2`, but flipped). * A sawtooth signal between 1 and -1 (like `saw2`, but flipped).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const isaw2 = isaw.toBipolar(); export const isaw2 = isaw.toBipolar();
@@ -66,12 +70,14 @@ export const isaw2 = isaw.toBipolar();
* A sine signal between -1 and 1 (like `sine`, but bipolar). * A sine signal between -1 and 1 (like `sine`, but bipolar).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t)); export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
/** /**
* A sine signal between 0 and 1. * A sine signal between 0 and 1.
* @return {Pattern} * @return {Pattern}
* @tags generators
* @example * @example
* n(sine.segment(16).range(0,15)) * n(sine.segment(16).range(0,15))
* .scale("C:minor") * .scale("C:minor")
@@ -83,6 +89,7 @@ export const sine = sine2.fromBipolar();
* A cosine signal between 0 and 1. * A cosine signal between 0 and 1.
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
* @example * @example
* n(stack(sine,cosine).segment(16).range(0,15)) * n(stack(sine,cosine).segment(16).range(0,15))
* .scale("C:minor") * .scale("C:minor")
@@ -94,12 +101,14 @@ export const cosine = sine._early(Fraction(1).div(4));
* A cosine signal between -1 and 1 (like `cosine`, but bipolar). * A cosine signal between -1 and 1 (like `cosine`, but bipolar).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const cosine2 = sine2._early(Fraction(1).div(4)); export const cosine2 = sine2._early(Fraction(1).div(4));
/** /**
* A square signal between 0 and 1. * A square signal between 0 and 1.
* @return {Pattern} * @return {Pattern}
* @tags generators
* @example * @example
* n(square.segment(4).range(0,7)).scale("C:minor") * n(square.segment(4).range(0,7)).scale("C:minor")
* *
@@ -110,6 +119,7 @@ export const square = signal((t) => Math.floor((t * 2) % 2));
* A square signal between -1 and 1 (like `square`, but bipolar). * A square signal between -1 and 1 (like `square`, but bipolar).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const square2 = square.toBipolar(); export const square2 = square.toBipolar();
@@ -117,6 +127,7 @@ export const square2 = square.toBipolar();
* A triangle signal between 0 and 1. * A triangle signal between 0 and 1.
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
* @example * @example
* n(tri.segment(8).range(0,7)).scale("C:minor") * n(tri.segment(8).range(0,7)).scale("C:minor")
* *
@@ -127,6 +138,7 @@ export const tri = fastcat(saw, isaw);
* A triangle signal between -1 and 1 (like `tri`, but bipolar). * A triangle signal between -1 and 1 (like `tri`, but bipolar).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const tri2 = fastcat(saw2, isaw2); export const tri2 = fastcat(saw2, isaw2);
@@ -134,6 +146,7 @@ export const tri2 = fastcat(saw2, isaw2);
* An inverted triangle signal between 1 and 0 (like `tri`, but flipped). * An inverted triangle signal between 1 and 0 (like `tri`, but flipped).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
* @example * @example
* n(itri.segment(8).range(0,7)).scale("C:minor") * n(itri.segment(8).range(0,7)).scale("C:minor")
* *
@@ -144,6 +157,7 @@ export const itri = fastcat(isaw, saw);
* An inverted triangle signal between -1 and 1 (like `itri`, but bipolar). * An inverted triangle signal between -1 and 1 (like `itri`, but bipolar).
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const itri2 = fastcat(isaw2, saw2); export const itri2 = fastcat(isaw2, saw2);
@@ -151,6 +165,7 @@ export const itri2 = fastcat(isaw2, saw2);
* A signal representing the cycle time. * A signal representing the cycle time.
* *
* @return {Pattern} * @return {Pattern}
* @tags generators
*/ */
export const time = signal(id); export const time = signal(id);
@@ -158,6 +173,7 @@ export const time = signal(id);
* The mouse's x position value ranges from 0 to 1. * The mouse's x position value ranges from 0 to 1.
* @name mousex * @name mousex
* @return {Pattern} * @return {Pattern}
* @tags external_io
* @example * @example
* n(mousex.segment(4).range(0,7)).scale("C:minor") * n(mousex.segment(4).range(0,7)).scale("C:minor")
* *
@@ -167,6 +183,7 @@ export const time = signal(id);
* The mouse's y position value ranges from 0 to 1. * The mouse's y position value ranges from 0 to 1.
* @name mousey * @name mousey
* @return {Pattern} * @return {Pattern}
* @tags external_io
* @example * @example
* n(mousey.segment(4).range(0,7)).scale("C:minor") * n(mousey.segment(4).range(0,7)).scale("C:minor")
* *
@@ -269,6 +286,7 @@ export const getRandsAtTime = (t, n = 1, seed = 0) => {
* precise RNG, try `useRNG('precise')`. * precise RNG, try `useRNG('precise')`.
* *
* @name useRNG * @name useRNG
* @tags generators, math
* @param {string} mod - Mode. One of 'legacy', 'precise' * @param {string} mod - Mode. One of 'legacy', 'precise'
* @example * @example
* useRNG('legacy') * useRNG('legacy')
@@ -280,6 +298,7 @@ export const useRNG = (mode = 'legacy') => (RNG_MODE = mode);
/** /**
* A discrete pattern of numbers from 0 to n-1 * A discrete pattern of numbers from 0 to n-1
* @tags generators
* @example * @example
* n(run(4)).scale("C4:pentatonic") * n(run(4)).scale("C4:pentatonic")
* // n("0 1 2 3").scale("C4:pentatonic") * // n("0 1 2 3").scale("C4:pentatonic")
@@ -290,6 +309,7 @@ export const run = (n) => saw.range(0, n).round().segment(n);
* Creates a binary pattern from a number. * Creates a binary pattern from a number.
* *
* @name binary * @name binary
* @tags generators
* @param {number} n - input number to convert to binary * @param {number} n - input number to convert to binary
* @example * @example
* "hh".s().struct(binary(5)) * "hh".s().struct(binary(5))
@@ -304,6 +324,7 @@ export const binary = (n) => {
* Creates a binary pattern from a number, padded to n bits long. * Creates a binary pattern from a number, padded to n bits long.
* *
* @name binaryN * @name binaryN
* @tags generators
* @param {number} n - input number to convert to binary * @param {number} n - input number to convert to binary
* @param {number} nBits - pattern length, defaults to 16 * @param {number} nBits - pattern length, defaults to 16
* @example * @example
@@ -321,6 +342,7 @@ export const binaryN = (n, nBits = 16) => {
* Creates a binary list pattern from a number. * Creates a binary list pattern from a number.
* *
* @name binaryL * @name binaryL
* @tags generators
* @param {number} n - input number to convert to binary * @param {number} n - input number to convert to binary
* s("saw").seg(8) * s("saw").seg(8)
* .partials(binaryL(irand(4096).add(1))) * .partials(binaryL(irand(4096).add(1)))
@@ -334,6 +356,7 @@ export const binaryL = (n) => {
* Creates a binary list pattern from a number, padded to n bits long. * Creates a binary list pattern from a number, padded to n bits long.
* *
* @name binaryNL * @name binaryNL
* @tags generators
* @param {number} n - input number to convert to binary * @param {number} n - input number to convert to binary
* @param {number} nBits - pattern length, defaults to 16 * @param {number} nBits - pattern length, defaults to 16
*/ */
@@ -353,6 +376,7 @@ export const binaryNL = (n, nBits = 16) => {
* Creates a list of random numbers of the given length * Creates a list of random numbers of the given length
* *
* @name randL * @name randL
* @tags generators
* @param {number} n Number of random numbers to sample * @param {number} n Number of random numbers to sample
* @example * @example
* s("saw").seg(16).n(irand(12)).scale("F1:minor") * s("saw").seg(16).n(irand(12)).scale("F1:minor")
@@ -384,6 +408,7 @@ const _rearrangeWith = (ipat, n, pat) => {
* Slices a pattern into the given number of parts, then plays those parts in random order. * Slices a pattern into the given number of parts, then plays those parts in random order.
* Each part will be played exactly once per cycle. * Each part will be played exactly once per cycle.
* @name shuffle * @name shuffle
* @tags temporal
* @example * @example
* note("c d e f").sound("piano").shuffle(4) * note("c d e f").sound("piano").shuffle(4)
* @example * @example
@@ -397,6 +422,7 @@ export const shuffle = register('shuffle', (n, pat) => {
* Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`, * Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`,
* but parts might be played more than once, or not at all, per cycle. * but parts might be played more than once, or not at all, per cycle.
* @name scramble * @name scramble
* @tags temporal
* @example * @example
* note("c d e f").sound("piano").scramble(4) * note("c d e f").sound("piano").scramble(4)
* @example * @example
@@ -409,6 +435,7 @@ export const scramble = register('scramble', (n, pat) => {
/** /**
* Modify a pattern by applying a function to the `randomSeed` control if present * Modify a pattern by applying a function to the `randomSeed` control if present
* *
* @tags math
* @param {Function} func Function from seed (or undefined) to seed (or undefined) * @param {Function} func Function from seed (or undefined) to seed (or undefined)
* @param {Pattern} pat Pattern to update * @param {Pattern} pat Pattern to update
* @returns Pattern * @returns Pattern
@@ -428,6 +455,7 @@ export const withSeed = (func, pat) => {
* that use randomness, like `shuffle` and `sometimes`. * that use randomness, like `shuffle` and `sometimes`.
* *
* @name seed * @name seed
* @tags math
* @param {number} n A new seed. Can be any number. * @param {number} n A new seed. Can be any number.
* @example * @example
* $: s("hh*4").degrade(); * $: s("hh*4").degrade();
@@ -441,6 +469,7 @@ export const seed = register('seed', (n, pat) => {
* A continuous pattern of random numbers, between 0 and 1. * A continuous pattern of random numbers, between 0 and 1.
* *
* @name rand * @name rand
* @tags generators
* @example * @example
* // randomly change the cutoff * // randomly change the cutoff
* s("bd*4,hh*8").cutoff(rand.range(500,8000)) * s("bd*4,hh*8").cutoff(rand.range(500,8000))
@@ -449,6 +478,7 @@ export const seed = register('seed', (n, pat) => {
export const rand = signal((t, controls) => getRandsAtTime(t, 1, controls.randSeed)); export const rand = signal((t, controls) => getRandsAtTime(t, 1, controls.randSeed));
/** /**
* A continuous pattern of random numbers, between -1 and 1 * A continuous pattern of random numbers, between -1 and 1
* @tags generators
*/ */
export const rand2 = rand.toBipolar(); export const rand2 = rand.toBipolar();
@@ -458,6 +488,7 @@ export const _brandBy = (p) => rand.fmap((x) => x < p);
* A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1 * A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1
* *
* @name brandBy * @name brandBy
* @tags generators
* @param {number} probability - a number between 0 and 1 * @param {number} probability - a number between 0 and 1
* @example * @example
* s("hh*10").pan(brandBy(0.2)) * s("hh*10").pan(brandBy(0.2))
@@ -468,6 +499,7 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
* A continuous pattern of 0 or 1 (binary random) * A continuous pattern of 0 or 1 (binary random)
* *
* @name brand * @name brand
* @tags generators
* @example * @example
* s("hh*10").pan(brand) * s("hh*10").pan(brand)
*/ */
@@ -479,6 +511,7 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
* A continuous pattern of random integers, between 0 and n-1. * A continuous pattern of random integers, between 0 and n-1.
* *
* @name irand * @name irand
* @tags generators
* @param {number} n max value (exclusive) * @param {number} n max value (exclusive)
* @example * @example
* // randomly select scale notes from 0 - 7 (= C to C) * // randomly select scale notes from 0 - 7 (= C to C)
@@ -501,6 +534,7 @@ export const __chooseWith = (pat, xs) => {
/** /**
* Choose from the list of values (or patterns of values) using the given * Choose from the list of values (or patterns of values) using the given
* pattern of numbers, which should be in the range of 0..1 * pattern of numbers, which should be in the range of 0..1
* @tags temporal
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -514,6 +548,7 @@ export const chooseWith = (pat, xs) => {
/** /**
* As with {chooseWith}, but the structure comes from the chosen values, rather * As with {chooseWith}, but the structure comes from the chosen values, rather
* than the pattern you're using to choose with. * than the pattern you're using to choose with.
* @tags temporal
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
* @returns {Pattern} * @returns {Pattern}
@@ -524,6 +559,7 @@ export const chooseInWith = (pat, xs) => {
/** /**
* Chooses randomly from the given list of elements. * Chooses randomly from the given list of elements.
* @tags temporal
* @param {...any} xs values / patterns to choose from. * @param {...any} xs values / patterns to choose from.
* @returns {Pattern} - a continuous pattern. * @returns {Pattern} - a continuous pattern.
* @example * @example
@@ -539,6 +575,7 @@ export const chooseOut = choose;
* Chooses from the given list of values (or patterns of values), according * Chooses from the given list of values (or patterns of values), according
* to the pattern that the method is called on. The pattern should be in * to the pattern that the method is called on. The pattern should be in
* the range 0 .. 1. * the range 0 .. 1.
* @tags temporal
* @param {...any} xs * @param {...any} xs
* @returns {Pattern} * @returns {Pattern}
*/ */
@@ -549,6 +586,7 @@ Pattern.prototype.choose = function (...xs) {
/** /**
* As with choose, but the pattern that this method is called on should be * As with choose, but the pattern that this method is called on should be
* in the range -1 .. 1 * in the range -1 .. 1
* @tags temporal
* @param {...any} xs * @param {...any} xs
* @returns {Pattern} * @returns {Pattern}
*/ */
@@ -558,6 +596,7 @@ Pattern.prototype.choose2 = function (...xs) {
/** /**
* Picks one of the elements at random each cycle. * Picks one of the elements at random each cycle.
* @tags temporal
* @synonyms randcat * @synonyms randcat
* @returns {Pattern} * @returns {Pattern}
* @example * @example
@@ -600,6 +639,7 @@ const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
/** /**
* Chooses randomly from the given list of elements by giving a probability to each element * Chooses randomly from the given list of elements by giving a probability to each element
* @tags temporal
* @param {...any} pairs arrays of value and weight * @param {...any} pairs arrays of value and weight
* @returns {Pattern} - a continuous pattern. * @returns {Pattern} - a continuous pattern.
* @example * @example
@@ -609,6 +649,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
/** /**
* Picks one of the elements at random each cycle by giving a probability to each element * Picks one of the elements at random each cycle by giving a probability to each element
* @tags temporal
* @synonyms wrandcat * @synonyms wrandcat
* @returns {Pattern} * @returns {Pattern}
* @example * @example
@@ -652,6 +693,7 @@ function _berlin(t, seed = 0) {
/** /**
* Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1. * Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1.
* *
* @tags generators
* @name perlin * @name perlin
* @example * @example
* // randomly change the cutoff * // randomly change the cutoff
@@ -664,6 +706,7 @@ export const perlin = signal((t, controls) => _perlin(t, controls.randSeed));
* Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful, * Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful,
* like perlin noise but with sawtooth waves), in the range 0..1. * like perlin noise but with sawtooth waves), in the range 0..1.
* *
* @tags generators
* @name berlin * @name berlin
* @example * @example
* // ascending arpeggios * // ascending arpeggios
@@ -684,6 +727,7 @@ export const degradeByWith = register(
* 0 = 0% chance of removal * 0 = 0% chance of removal
* 1 = 100% chance of removal * 1 = 100% chance of removal
* *
* @tags temporal
* @name degradeBy * @name degradeBy
* @memberof Pattern * @memberof Pattern
* @param {number} amount - a number between 0 and 1 * @param {number} amount - a number between 0 and 1
@@ -709,6 +753,7 @@ export const degradeBy = register(
* *
* Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)` * Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)`
* *
* @tags temporal
* @name degrade * @name degrade
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -725,6 +770,7 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, t
* 1 = 0% chance of removal * 1 = 0% chance of removal
* Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
* *
* @tags temporal
* @name undegradeBy * @name undegradeBy
* @memberof Pattern * @memberof Pattern
* @param {number} amount - a number between 0 and 1 * @param {number} amount - a number between 0 and 1
@@ -753,6 +799,7 @@ export const undegradeBy = register(
* Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` * Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)`
* Events that would be removed by degrade are let through by undegrade and vice versa (see second example). * Events that would be removed by degrade are let through by undegrade and vice versa (see second example).
* *
* @tags temporal
* @name undegrade * @name undegrade
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -771,6 +818,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t
* Randomly applies the given function by the given probability. * Randomly applies the given function by the given probability.
* Similar to `someCyclesBy` * Similar to `someCyclesBy`
* *
* @tags temporal
* @name sometimesBy * @name sometimesBy
* @memberof Pattern * @memberof Pattern
* @param {number | Pattern} probability - a number between 0 and 1 * @param {number | Pattern} probability - a number between 0 and 1
@@ -790,6 +838,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
* *
* Applies the given function with a 50% chance * Applies the given function with a 50% chance
* *
* @tags temporal
* @name sometimes * @name sometimes
* @memberof Pattern * @memberof Pattern
* @param {function} function - the transformation to apply * @param {function} function - the transformation to apply
@@ -811,6 +860,7 @@ export const sometimes = register('sometimes', function (func, pat) {
* @param {number | Pattern} probability - a number between 0 and 1 * @param {number | Pattern} probability - a number between 0 and 1
* @param {function} function - the transformation to apply * @param {function} function - the transformation to apply
* @returns Pattern * @returns Pattern
* @tags temporal
* @example * @example
* s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5")) * s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5"))
*/ */
@@ -833,6 +883,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat)
* @name someCycles * @name someCycles
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @tags temporal
* @example * @example
* s("bd,hh*8").someCycles(x=>x.speed("0.5")) * s("bd,hh*8").someCycles(x=>x.speed("0.5"))
*/ */
@@ -847,6 +898,7 @@ export const someCycles = register('someCycles', function (func, pat) {
* @name often * @name often
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @tags temporal
* @example * @example
* s("hh*8").often(x=>x.speed("0.5")) * s("hh*8").often(x=>x.speed("0.5"))
*/ */
@@ -861,6 +913,7 @@ export const often = register('often', function (func, pat) {
* @name rarely * @name rarely
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @tags temporal
* @example * @example
* s("hh*8").rarely(x=>x.speed("0.5")) * s("hh*8").rarely(x=>x.speed("0.5"))
*/ */
@@ -872,6 +925,7 @@ export const rarely = register('rarely', function (func, pat) {
* *
* Shorthand for `.sometimesBy(0.1, fn)` * Shorthand for `.sometimesBy(0.1, fn)`
* *
* @tags temporal
* @name almostNever * @name almostNever
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -886,6 +940,7 @@ export const almostNever = register('almostNever', function (func, pat) {
* *
* Shorthand for `.sometimesBy(0.9, fn)` * Shorthand for `.sometimesBy(0.9, fn)`
* *
* @tags temporal
* @name almostAlways * @name almostAlways
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -900,6 +955,7 @@ export const almostAlways = register('almostAlways', function (func, pat) {
* *
* Shorthand for `.sometimesBy(0, fn)` (never calls fn) * Shorthand for `.sometimesBy(0, fn)` (never calls fn)
* *
* @tags temporal
* @name never * @name never
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -914,6 +970,7 @@ export const never = register('never', function (_, pat) {
* *
* Shorthand for `.sometimesBy(1, fn)` (always calls fn) * Shorthand for `.sometimesBy(1, fn)` (always calls fn)
* *
* @tags temporal
* @name always * @name always
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -942,6 +999,7 @@ export function _keyDown(keyname) {
* Do something on a keypress, or array of keypresses * Do something on a keypress, or array of keypresses
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
* *
* @tags external_io
* @name whenKey * @name whenKey
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -958,6 +1016,7 @@ export const whenKey = register('whenKey', function (input, func, pat) {
* returns true when a key or array of keys is held * returns true when a key or array of keys is held
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) * [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
* *
* @tags external_io
* @name keyDown * @name keyDown
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
@@ -975,6 +1034,8 @@ export const keyDown = register('keyDown', function (pat) {
* event durations, from the pattern that it is combined with. * event durations, from the pattern that it is combined with.
* For example `cyclesPer.struct("1 1 [1 1] 1")` would give the same as `"0.25 0.25 [0.125 0.125] 0.25"`. * For example `cyclesPer.struct("1 1 [1 1] 1")` would give the same as `"0.25 0.25 [0.125 0.125] 0.25"`.
* See also its reciprocal, `per`, also known as `perCycle`. * See also its reciprocal, `per`, also known as `perCycle`.
*
* @tags temporal
* @example * @example
* // Shorter events are lower in pitch * // Shorter events are lower in pitch
* sound("saw saw [saw saw] saw") * sound("saw saw [saw saw] saw")
@@ -993,6 +1054,7 @@ export const cyclesPer = new Pattern(function (state) {
* event durations, from the pattern that it is combined with. * event durations, from the pattern that it is combined with.
* For example `per.struct("1 1 [1 1] 1")` would give the same as `"4 4 [8 8] 4"`. * For example `per.struct("1 1 [1 1] 1")` would give the same as `"4 4 [8 8] 4"`.
* See also its reciprocal, `cyclesPer`. * See also its reciprocal, `cyclesPer`.
* @tags temporal
* @synonyms perCycle * @synonyms perCycle
* @example * @example
* // Shorter events are more distorted * // Shorter events are more distorted
@@ -1010,6 +1072,7 @@ export const perCycle = per;
* particular, where the event duration halves, the * particular, where the event duration halves, the
* returned value increases by one. `perx.struct("1 1 [1 [1 1]] 1")` would therefore be * returned value increases by one. `perx.struct("1 1 [1 [1 1]] 1")` would therefore be
* the same as `"3 3 [4 [5 5]] 3"`. * the same as `"3 3 [4 [5 5]] 3"`.
* @tags temporal
*/ */
export const perx = new Pattern(function (state) { export const perx = new Pattern(function (state) {
const n = Fraction(1).div(state.span.duration); const n = Fraction(1).div(state.span.duration);
+2
View File
@@ -135,6 +135,8 @@ export async function loadOrc(url) {
* p4 -- MIDI key number (as a real number, not an integer but in [0, 127]. * p4 -- MIDI key number (as a real number, not an integer but in [0, 127].
* p5 -- MIDI velocity (as a real number, not an integer but in [0, 127]. * p5 -- MIDI velocity (as a real number, not an integer but in [0, 127].
* p6 -- Strudel controls, as a string. * p6 -- Strudel controls, as a string.
*
* @tags external_io
*/ */
export const csoundm = register('csoundm', (instrument, pat) => { export const csoundm = register('csoundm', (instrument, pat) => {
let p1 = instrument; let p1 = instrument;
+2
View File
@@ -42,6 +42,7 @@ const getValue = (e) => {
* *
* @name pianoroll * @name pianoroll
* @synonyms punchcard * @synonyms punchcard
* @tags visualization
* @param {Object} options Object containing all the optional following parameters as key value pairs: * @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4 * @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4
* @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5 * @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5
@@ -299,6 +300,7 @@ Pattern.prototype.punchcard = function (options) {
* Supports all the same options as pianoroll. * Supports all the same options as pianoroll.
* *
* @name wordfall * @name wordfall
* @tags visualization
*/ */
Pattern.prototype.wordfall = function (options) { Pattern.prototype.wordfall = function (options) {
return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options }); return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options });
+1
View File
@@ -116,6 +116,7 @@ export function pitchwheel({
/** /**
* Renders a pitch circle to visualize frequencies within one octave * Renders a pitch circle to visualize frequencies within one octave
* @name pitchwheel * @name pitchwheel
* @tags visualization
* @param {number} hapcircles * @param {number} hapcircles
* @param {number} circle * @param {number} circle
* @param {number} edo * @param {number} edo
+1
View File
@@ -129,6 +129,7 @@ function drawSpiral(options) {
* Displays a spiral visual. * Displays a spiral visual.
* *
* @name spiral * @name spiral
* @tags visualization
* @param {Object} options Object containing all the optional following parameters as key value pairs: * @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {number} stretch controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees * @param {number} stretch controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees
* @param {number} size the diameter of the spiral * @param {number} size the diameter of the spiral
+5
View File
@@ -138,6 +138,7 @@ function githubPath(base, subpath = '') {
/** /**
* configures the default midimap, which is used when no "midimap" port is set * configures the default midimap, which is used when no "midimap" port is set
* @tags external_io
* @example * @example
* defaultmidimap({ lpf: 74 }) * defaultmidimap({ lpf: 74 })
* $: note("c a f e").midi(); * $: note("c a f e").midi();
@@ -151,6 +152,7 @@ let loadCache = {};
/** /**
* Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers. * Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers.
* @tags external_io
* @example * @example
* midimaps({ mymap: { lpf: 74 } }) * midimaps({ mymap: { lpf: 74 } })
* $: note("c a f e") * $: note("c a f e")
@@ -305,6 +307,7 @@ function sendNote(note, velocity, duration, device, midichan, targetTime) {
/** /**
* MIDI output: Opens a MIDI output port. * MIDI output: Opens a MIDI output port.
* @tags external_io
* @param {string | number} midiport MIDI device name or index defaulting to 0 * @param {string | number} midiport MIDI device name or index defaulting to 0
* @param {object} options Additional MIDI configuration options * @param {object} options Additional MIDI configuration options
* @example * @example
@@ -526,6 +529,7 @@ async function _initialize(input) {
* The output is a function that accepts a midi cc value to query as well as (optionally) a midi channel * The output is a function that accepts a midi cc value to query as well as (optionally) a midi channel
* *
* @name midin * @name midin
* @tags external_io
* @param {string | number} input MIDI device name or index defaulting to 0 * @param {string | number} input MIDI device name or index defaulting to 0
* @returns {function(number, number=): Pattern} A function from (cc, channel?) to a pattern. * @returns {function(number, number=): Pattern} A function from (cc, channel?) to a pattern.
* When queried, the pattern will produces the most recently received midi value (normalized to 0 to 1) * When queried, the pattern will produces the most recently received midi value (normalized to 0 to 1)
@@ -573,6 +577,7 @@ export async function midin(input) {
* note durations * note durations
* *
* @name midikeys * @name midikeys
* @tags external_io
* @param {string | number} input MIDI device name or index defaulting to 0 * @param {string | number} input MIDI device name or index defaulting to 0
* @returns {function((number | Pattern)=): Pattern} A function that produces a pattern. * @returns {function((number | Pattern)=): Pattern} A function that produces a pattern.
* When queried, the pattern will produces the most recently played midi notes and velocities, * When queried, the pattern will produces the most recently played midi notes and velocities,
+15
View File
@@ -7,6 +7,7 @@ import { signal } from '../core/signal.mjs';
* @name accelerationX * @name accelerationX
* @return {Pattern} * @return {Pattern}
* @synonyms accX * @synonyms accX
* @tags external_io
* @example * @example
* n(accelerationX.segment(4).range(0,7)).scale("C:minor") * n(accelerationX.segment(4).range(0,7)).scale("C:minor")
* *
@@ -17,6 +18,7 @@ import { signal } from '../core/signal.mjs';
* @name accelerationY * @name accelerationY
* @return {Pattern} * @return {Pattern}
* @synonyms accY * @synonyms accY
* @tags external_io
* @example * @example
* n(accelerationY.segment(4).range(0,7)).scale("C:minor") * n(accelerationY.segment(4).range(0,7)).scale("C:minor")
* *
@@ -27,6 +29,7 @@ import { signal } from '../core/signal.mjs';
* @name accelerationZ * @name accelerationZ
* @return {Pattern} * @return {Pattern}
* @synonyms accZ * @synonyms accZ
* @tags external_io
* @example * @example
* n(accelerationZ.segment(4).range(0,7)).scale("C:minor") * n(accelerationZ.segment(4).range(0,7)).scale("C:minor")
* *
@@ -37,6 +40,7 @@ import { signal } from '../core/signal.mjs';
* @name gravityX * @name gravityX
* @return {Pattern} * @return {Pattern}
* @synonyms gravX * @synonyms gravX
* @tags external_io
* @example * @example
* n(gravityX.segment(4).range(0,7)).scale("C:minor") * n(gravityX.segment(4).range(0,7)).scale("C:minor")
* *
@@ -47,6 +51,7 @@ import { signal } from '../core/signal.mjs';
* @name gravityY * @name gravityY
* @return {Pattern} * @return {Pattern}
* @synonyms gravY * @synonyms gravY
* @tags external_io
* @example * @example
* n(gravityY.segment(4).range(0,7)).scale("C:minor") * n(gravityY.segment(4).range(0,7)).scale("C:minor")
* *
@@ -57,6 +62,7 @@ import { signal } from '../core/signal.mjs';
* @name gravityZ * @name gravityZ
* @return {Pattern} * @return {Pattern}
* @synonyms gravZ * @synonyms gravZ
* @tags external_io
* @example * @example
* n(gravityZ.segment(4).range(0,7)).scale("C:minor") * n(gravityZ.segment(4).range(0,7)).scale("C:minor")
* *
@@ -67,6 +73,7 @@ import { signal } from '../core/signal.mjs';
* @name rotationAlpha * @name rotationAlpha
* @return {Pattern} * @return {Pattern}
* @synonyms rotA, rotZ, rotationZ * @synonyms rotA, rotZ, rotationZ
* @tags external_io
* @example * @example
* n(rotationAlpha.segment(4).range(0,7)).scale("C:minor") * n(rotationAlpha.segment(4).range(0,7)).scale("C:minor")
* *
@@ -77,6 +84,7 @@ import { signal } from '../core/signal.mjs';
* @name rotationBeta * @name rotationBeta
* @return {Pattern} * @return {Pattern}
* @synonyms rotB, rotX, rotationX * @synonyms rotB, rotX, rotationX
* @tags external_io
* @example * @example
* n(rotationBeta.segment(4).range(0,7)).scale("C:minor") * n(rotationBeta.segment(4).range(0,7)).scale("C:minor")
* *
@@ -87,6 +95,7 @@ import { signal } from '../core/signal.mjs';
* @name rotationGamma * @name rotationGamma
* @return {Pattern} * @return {Pattern}
* @synonyms rotG, rotY, rotationY * @synonyms rotG, rotY, rotationY
* @tags external_io
* @example * @example
* n(rotationGamma.segment(4).range(0,7)).scale("C:minor") * n(rotationGamma.segment(4).range(0,7)).scale("C:minor")
* *
@@ -97,6 +106,7 @@ import { signal } from '../core/signal.mjs';
* @name orientationAlpha * @name orientationAlpha
* @return {Pattern} * @return {Pattern}
* @synonyms oriA, oriZ, orientationZ * @synonyms oriA, oriZ, orientationZ
* @tags external_io
* @example * @example
* n(orientationAlpha.segment(4).range(0,7)).scale("C:minor") * n(orientationAlpha.segment(4).range(0,7)).scale("C:minor")
* *
@@ -107,6 +117,7 @@ import { signal } from '../core/signal.mjs';
* @name orientationBeta * @name orientationBeta
* @return {Pattern} * @return {Pattern}
* @synonyms oriB, oriX, orientationX * @synonyms oriB, oriX, orientationX
* @tags external_io
* @example * @example
* n(orientationBeta.segment(4).range(0,7)).scale("C:minor") * n(orientationBeta.segment(4).range(0,7)).scale("C:minor")
* *
@@ -117,6 +128,7 @@ import { signal } from '../core/signal.mjs';
* @name orientationGamma * @name orientationGamma
* @return {Pattern} * @return {Pattern}
* @synonyms oriG, oriY, orientationY * @synonyms oriG, oriY, orientationY
* @tags external_io
* @example * @example
* n(orientationGamma.segment(4).range(0,7)).scale("C:minor") * n(orientationGamma.segment(4).range(0,7)).scale("C:minor")
* *
@@ -127,6 +139,7 @@ import { signal } from '../core/signal.mjs';
* @name absoluteOrientationAlpha * @name absoluteOrientationAlpha
* @return {Pattern} * @return {Pattern}
* @synonyms absOriA, absOriZ, absoluteOrientationZ * @synonyms absOriA, absOriZ, absoluteOrientationZ
* @tags external_io
* @example * @example
* n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor") * n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor")
* *
@@ -137,6 +150,7 @@ import { signal } from '../core/signal.mjs';
* @name absoluteOrientationBeta * @name absoluteOrientationBeta
* @return {Pattern} * @return {Pattern}
* @synonyms absOriB, absOriX, absoluteOrientationX * @synonyms absOriB, absOriX, absoluteOrientationX
* @tags external_io
* @example * @example
* n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor") * n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor")
* *
@@ -147,6 +161,7 @@ import { signal } from '../core/signal.mjs';
* @name absoluteOrientationGamma * @name absoluteOrientationGamma
* @return {Pattern} * @return {Pattern}
* @synonyms absOriG, absOriY, absoluteOrientationY * @synonyms absOriG, absOriY, absoluteOrientationY
* @tags external_io
* @example * @example
* n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor") * n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor")
* *
+1
View File
@@ -79,6 +79,7 @@ export async function oscTrigger(hap, currentTime, cps = 1, targetTime) {
* For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output/) * For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output/)
* *
* @name osc * @name osc
* @tags external_io
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
*/ */
+2 -1
View File
@@ -98,6 +98,7 @@ export const connectLFO = (id, params, nodeTracker) => {
fxi = 'main', fxi = 'main',
depth = 1, depth = 1,
depthabs, depthabs,
retrig = 0,
...filteredParams ...filteredParams
} = params; } = params;
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl); const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl);
@@ -109,7 +110,7 @@ export const connectLFO = (id, params, nodeTracker) => {
const modParams = { const modParams = {
...filteredParams, ...filteredParams,
frequency: sync !== undefined ? sync * cps : rate, frequency: sync !== undefined ? sync * cps : rate,
time: cycle / cps, time: retrig > 0.5 ? 0 : cycle / cps,
depth: depthValue, depth: depthValue,
min, min,
max, max,
+22 -7
View File
@@ -35,7 +35,9 @@ class OLAProcessor extends AudioWorkletProcessor {
} }
/** Handles dynamic reallocation of input/output channels buffer /** Handles dynamic reallocation of input/output channels buffer
(channel numbers may lety during lifecycle) **/ * (channel numbers may vary during lifecycle)
* @tags internals
**/
reallocateChannelsIfNeeded(inputs, outputs) { reallocateChannelsIfNeeded(inputs, outputs) {
for (let i = 0; i < this.nbInputs; i++) { for (let i = 0; i < this.nbInputs; i++) {
let nbChannels = inputs[i].length; let nbChannels = inputs[i].length;
@@ -88,7 +90,10 @@ class OLAProcessor extends AudioWorkletProcessor {
} }
} }
/** Read next web audio block to input buffers **/ /**
* Read next web audio block to input buffers
* @tags internals
**/
readInputs(inputs) { readInputs(inputs) {
// when playback is paused, we may stop receiving new samples // when playback is paused, we may stop receiving new samples
if (inputs[0].length && inputs[0][0].length == 0) { if (inputs[0].length && inputs[0][0].length == 0) {
@@ -108,7 +113,9 @@ class OLAProcessor extends AudioWorkletProcessor {
} }
} }
/** Write next web audio block from output buffers **/ /** Write next web audio block from output buffers
* @tags internals
**/
writeOutputs(outputs) { writeOutputs(outputs) {
for (let i = 0; i < this.nbInputs; i++) { for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < this.inputBuffers[i].length; j++) { for (let j = 0; j < this.inputBuffers[i].length; j++) {
@@ -118,7 +125,9 @@ class OLAProcessor extends AudioWorkletProcessor {
} }
} }
/** Shift left content of input buffers to receive new web audio block **/ /** Shift left content of input buffers to receive new web audio block
* @tags internals
**/
shiftInputBuffers() { shiftInputBuffers() {
for (let i = 0; i < this.nbInputs; i++) { for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < this.inputBuffers[i].length; j++) { for (let j = 0; j < this.inputBuffers[i].length; j++) {
@@ -127,7 +136,9 @@ class OLAProcessor extends AudioWorkletProcessor {
} }
} }
/** Shift left content of output buffers to receive new web audio block **/ /** Shift left content of output buffers to receive new web audio block
* @tags internals
**/
shiftOutputBuffers() { shiftOutputBuffers() {
for (let i = 0; i < this.nbOutputs; i++) { for (let i = 0; i < this.nbOutputs; i++) {
for (let j = 0; j < this.outputBuffers[i].length; j++) { for (let j = 0; j < this.outputBuffers[i].length; j++) {
@@ -137,7 +148,9 @@ class OLAProcessor extends AudioWorkletProcessor {
} }
} }
/** Copy contents of input buffers to buffer actually sent to process **/ /** Copy contents of input buffers to buffer actually sent to process
* @tags internals
**/
prepareInputBuffersToSend() { prepareInputBuffersToSend() {
for (let i = 0; i < this.nbInputs; i++) { for (let i = 0; i < this.nbInputs; i++) {
for (let j = 0; j < this.inputBuffers[i].length; j++) { for (let j = 0; j < this.inputBuffers[i].length; j++) {
@@ -146,7 +159,9 @@ class OLAProcessor extends AudioWorkletProcessor {
} }
} }
/** Add contents of output buffers just processed to output buffers **/ /** Add contents of output buffers just processed to output buffers
* @tags internals
**/
handleOutputBuffersToRetrieve() { handleOutputBuffersToRetrieve() {
for (let i = 0; i < this.nbOutputs; i++) { for (let i = 0; i < this.nbOutputs; i++) {
for (let j = 0; j < this.outputBuffers[i].length; j++) { for (let j = 0; j < this.outputBuffers[i].length; j++) {
+1
View File
@@ -37,6 +37,7 @@
}, },
"dependencies": { "dependencies": {
"@kabelsalat/lib": "^0.4.1", "@kabelsalat/lib": "^0.4.1",
"@kabelsalat/web": "^0.4.1",
"nanostores": "^0.11.3" "nanostores": "^0.11.3"
}, },
"engines": { "engines": {
+5 -2
View File
@@ -18,6 +18,7 @@ var reverbGen = {};
/** Generates a reverb impulse response. /** Generates a reverb impulse response.
@tags internals
@param {!Object} params TODO: Document the properties. @param {!Object} params TODO: Document the properties.
@param {!function(!AudioBuffer)} callback Function to call when @param {!function(!AudioBuffer)} callback Function to call when
the impulse response has been generated. The impulse response the impulse response has been generated. The impulse response
@@ -50,7 +51,7 @@ reverbGen.generateReverb = function (params, callback) {
/** Creates a canvas element showing a graph of the given data. /** Creates a canvas element showing a graph of the given data.
@tags internals
@param {!Float32Array} data An array of numbers, or a Float32Array. @param {!Float32Array} data An array of numbers, or a Float32Array.
@param {number} width Width in pixels of the canvas. @param {number} width Width in pixels of the canvas.
@param {number} height Height in pixels of the canvas. @param {number} height Height in pixels of the canvas.
@@ -81,7 +82,9 @@ reverbGen.generateGraph = function (data, width, height, min, max) {
@param {number} lpFreqEnd @param {number} lpFreqEnd
@param {number} lpFreqEndAt @param {number} lpFreqEndAt
@param {!function(!AudioBuffer)} callback May be called @param {!function(!AudioBuffer)} callback May be called
immediately within the current execution context, or later.*/ immediately within the current execution context, or later.
@tags internals
*/
var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) { var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) {
if (lpFreqStart == 0) { if (lpFreqStart == 0) {
callback(input); callback(input);
+1
View File
@@ -235,6 +235,7 @@ export async function fetchSampleMap(url) {
/** /**
* Loads a collection of samples to use with `s` * Loads a collection of samples to use with `s`
* @tags samples
* @example * @example
* samples('github:tidalcycles/dirt-samples'); * samples('github:tidalcycles/dirt-samples');
* s("[bd ~]*2, [~ hh]*2, ~ sd") * s("[bd ~]*2, [~ hh]*2, ~ sd")
+22
View File
@@ -40,6 +40,7 @@ export let maxPolyphony = DEFAULT_MAX_POLYPHONY;
* start to die out in first-in-first-out order once the max polyphony has been hit * start to die out in first-in-first-out order once the max polyphony has been hit
* *
* @name setMaxPolyphony * @name setMaxPolyphony
* @tags fx, superdough
* @param {number} Max polyphony. Defaults to 128 * @param {number} Max polyphony. Defaults to 128
* @example * @example
* setMaxPolyphony(4) * setMaxPolyphony(4)
@@ -73,6 +74,7 @@ export function applyGainCurve(val) {
* quadratic, exponential, etc. rather than linear * quadratic, exponential, etc. rather than linear
* *
* @name setGainCurve * @name setGainCurve
* @tags fx, superdough
* @param {Function} function to apply to all gain values * @param {Function} function to apply to all gain values
* @example * @example
* setGainCurve((x) => x * x) // quadratic gain * setGainCurve((x) => x * x) // quadratic gain
@@ -128,6 +130,8 @@ async function aliasBankPath(path) {
* Optionally accepts a single argument string of a path to a JSON file containing bank aliases. * Optionally accepts a single argument string of a path to a JSON file containing bank aliases.
* @param {string} bank - The bank to alias * @param {string} bank - The bank to alias
* @param {string} alias - The alias to use for the bank * @param {string} alias - The alias to use for the bank
*
* @tags samples
*/ */
export async function aliasBank(...args) { export async function aliasBank(...args) {
switch (args.length) { switch (args.length) {
@@ -146,6 +150,7 @@ export async function aliasBank(...args) {
/** /**
* Register an alias for a sound. * Register an alias for a sound.
* @tags samples
* @param {string} original - The original sound name * @param {string} original - The original sound name
* @param {string} alias - The alias to use for the sound * @param {string} alias - The alias to use for the sound
*/ */
@@ -255,6 +260,14 @@ export function loadWorklets() {
return workletsLoading; return workletsLoading;
} }
let kabel;
async function initKabelsalat() {
const { SalatRepl } = await import('@kabelsalat/web');
logger('[kabelsalat] ready');
kabel = new SalatRepl({ localScope: true });
return kabel;
}
// this function should be called on first user interaction (to avoid console warning) // this function should be called on first user interaction (to avoid console warning)
export async function initAudio(options = {}) { export async function initAudio(options = {}) {
const { const {
@@ -301,6 +314,7 @@ export async function initAudio(options = {}) {
} catch (err) { } catch (err) {
console.warn('could not load AudioWorklet effects', err); console.warn('could not load AudioWorklet effects', err);
} }
await initKabelsalat();
logger('[superdough] ready'); logger('[superdough] ready');
} }
let audioReady; let audioReady;
@@ -436,6 +450,14 @@ class Chain {
} }
} }
const compileKabel = (code) => {
if (!kabel) {
throw new Error('kabelsalat not loaded');
}
const node = kabel.evaluate(code);
return node.compile({ log: false });
};
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => { export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
// mapping from main FX and numbered FX chains to nodes // mapping from main FX and numbered FX chains to nodes
const nodes = { main: {} }; const nodes = { main: {} };
+3 -1
View File
@@ -110,7 +110,9 @@ export function getCommonSampleInfo(hapValue, bank) {
return { transpose, url, index, midi, label }; return { transpose, url, index, midi, label };
} }
/** Selects entries from `source` and renames them via `map` */ /** Selects entries from `source` and renames them via `map`
* @tags internals
*/
export const pickAndRename = (source, map) => { export const pickAndRename = (source, map) => {
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]])); return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
}; };
+1
View File
@@ -186,6 +186,7 @@ export function registerWaveTable(key, tables, params) {
* Loads a collection of wavetables to use with `s` * Loads a collection of wavetables to use with `s`
* *
* @name tables * @name tables
* @tags fx
*/ */
export const tables = async (url, frameLen, json, options = {}) => { export const tables = async (url, frameLen, json, options = {}) => {
if (json !== undefined) return _processTables(json, url, frameLen); if (json !== undefined) return _processTables(json, url, frameLen);
+15 -5
View File
@@ -646,14 +646,18 @@ class PhaseVocoderProcessor extends OLAProcessor {
this.timeCursor += this.hopSize; this.timeCursor += this.hopSize;
} }
/** Apply Hann window in-place */ /** Apply Hann window in-place
* @tags internals
*/
applyHannWindow(input) { applyHannWindow(input) {
for (let i = 0; i < this.blockSize; i++) { for (let i = 0; i < this.blockSize; i++) {
input[i] *= this.hannWindow[i] * 1.62; input[i] *= this.hannWindow[i] * 1.62;
} }
} }
/** Compute squared magnitudes for peak finding **/ /** Compute squared magnitudes for peak finding
* @tags internals
**/
computeMagnitudes() { computeMagnitudes() {
let i = 0, let i = 0,
j = 0; j = 0;
@@ -667,7 +671,9 @@ class PhaseVocoderProcessor extends OLAProcessor {
} }
} }
/** Find peaks in spectrum magnitudes **/ /** Find peaks in spectrum magnitudes
* @tags internals
**/
findPeaks() { findPeaks() {
this.nbPeaks = 0; this.nbPeaks = 0;
let i = 2; let i = 2;
@@ -688,7 +694,9 @@ class PhaseVocoderProcessor extends OLAProcessor {
} }
} }
/** Shift peaks and regions of influence by pitchFactor into new specturm */ /** Shift peaks and regions of influence by pitchFactor into new specturm
* @tags internals
*/
shiftPeaks(pitchFactor) { shiftPeaks(pitchFactor) {
// zero-fill new spectrum // zero-fill new spectrum
this.freqComplexBufferShifted.fill(0); this.freqComplexBufferShifted.fill(0);
@@ -841,7 +849,9 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
registerProcessor('pulse-oscillator', PulseOscillatorProcessor); registerProcessor('pulse-oscillator', PulseOscillatorProcessor);
/** BYTE BEATS */ /** BYTE BEATS
* @tags internals
*/
const chyx = { const chyx = {
/*bit*/ bitC: function (x, y, z) { /*bit*/ bitC: function (x, y, z) {
return x & y ? z : 0; return x & y ? z : 0;
+1
View File
@@ -19,6 +19,7 @@ function applyGainCurve(val) {
* @param {number} a - Signal A (can be a single value or an array value in buffer processing). * @param {number} a - Signal A (can be a single value or an array value in buffer processing).
* @param {number} b - Signal B (can be a single value or an array value in buffer processing). * @param {number} b - Signal B (can be a single value or an array value in buffer processing).
* @param {number} m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix). * @param {number} m - Crossfade parameter (0.0 = all A, 1.0 = all B, 0.5 = equal mix).
* @tags internals
* @returns {number} Crossfaded output value. * @returns {number} Crossfaded output value.
*/ */
function crossfade(a, b, m) { function crossfade(a, b, m) {
+3
View File
@@ -100,6 +100,7 @@ function scaleOffset(scale, offset, note) {
* - 5P = perfect fifth * - 5P = perfect fifth
* - 5d = diminished fifth * - 5d = diminished fifth
* *
* @tags music_theory
* @param {string | number} amount Either number of semitones or interval string. * @param {string | number} amount Either number of semitones or interval string.
* @returns Pattern * @returns Pattern
* @memberof Pattern * @memberof Pattern
@@ -154,6 +155,7 @@ export const { transpose, trans } = register(['transpose', 'trans'], function tr
* *
* @memberof Pattern * @memberof Pattern
* @name scaleTranspose * @name scaleTranspose
* @tags music_theory
* @param {offset} offset number of steps inside the scale * @param {offset} offset number of steps inside the scale
* @returns Pattern * @returns Pattern
* @synonyms scaleTrans, strans * @synonyms scaleTrans, strans
@@ -244,6 +246,7 @@ function _getNearestScaleNote(scaleName, note, preferHigher = true) {
* The root note defaults to octave 3, if no octave number is given. * The root note defaults to octave 3, if no octave number is given.
* *
* @name scale * @name scale
* @tags music_theory
* @param {string} scale Name of scale * @param {string} scale Name of scale
* @returns Pattern * @returns Pattern
* @example * @example
+4
View File
@@ -90,6 +90,7 @@ export const setVoicingRange = (name, range) => addVoicings(name, voicingRegistr
* Adds a new custom voicing dictionary. * Adds a new custom voicing dictionary.
* *
* @name addVoicings * @name addVoicings
* @tags music_theory
* @memberof Pattern * @memberof Pattern
* @param {string} name identifier for the voicing dictionary * @param {string} name identifier for the voicing dictionary
* @param {Object} dictionary maps chord symbol to possible voicings * @param {Object} dictionary maps chord symbol to possible voicings
@@ -133,6 +134,7 @@ const getVoicing = (chord, dictionaryName, lastVoicing) => {
* Uses [chord-voicings package](https://github.com/felixroos/chord-voicings#chord-voicings). * Uses [chord-voicings package](https://github.com/felixroos/chord-voicings#chord-voicings).
* *
* @name voicings * @name voicings
* @tags music_theory
* @memberof Pattern * @memberof Pattern
* @param {string} dictionary which voicing dictionary to use. * @param {string} dictionary which voicing dictionary to use.
* @returns Pattern * @returns Pattern
@@ -157,6 +159,7 @@ export const voicings = register('voicings', function (dictionary, pat) {
* Maps the chords of the incoming pattern to root notes in the given octave. * Maps the chords of the incoming pattern to root notes in the given octave.
* *
* @name rootNotes * @name rootNotes
* @tags music_theory
* @memberof Pattern * @memberof Pattern
* @param {octave} octave octave to use * @param {octave} octave octave to use
* @returns Pattern * @returns Pattern
@@ -189,6 +192,7 @@ export const rootNotes = register('rootNotes', function (octave, pat) {
* If you pass a pattern of strings to voicing, they will be interpreted as chords. * If you pass a pattern of strings to voicing, they will be interpreted as chords.
* *
* @name voicing * @name voicing
* @tags music_theory
* @returns Pattern * @returns Pattern
* @example * @example
* n("0 1 2 3").chord("<C Am F G>").voicing() * n("0 1 2 3").chord("<C Am F G>").voicing()
+2
View File
@@ -98,6 +98,7 @@ function clearScreen(smear = 0, smearRGB = `0,0,0`, ctx = getDrawContext()) {
/** /**
* Renders an oscilloscope for the frequency domain of the audio signal. * Renders an oscilloscope for the frequency domain of the audio signal.
* @name fscope * @name fscope
* @tags visualization
* @param {string} color line color as hex or color name. defaults to white. * @param {string} color line color as hex or color name. defaults to white.
* @param {number} scale scales the y-axis. Defaults to 0.25 * @param {number} scale scales the y-axis. Defaults to 0.25
* @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen * @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen
@@ -122,6 +123,7 @@ Pattern.prototype.fscope = function (config = {}) {
* Renders an oscilloscope for the time domain of the audio signal. * Renders an oscilloscope for the time domain of the audio signal.
* @name scope * @name scope
* @synonyms tscope * @synonyms tscope
* @tags visualization
* @param {object} config optional config with options: * @param {object} config optional config with options:
* @param {boolean} align if 1, the scope will be aligned to the first zero crossing. defaults to 1 * @param {boolean} align if 1, the scope will be aligned to the first zero crossing. defaults to 1
* @param {string} color line color as hex or color name. defaults to white. * @param {string} color line color as hex or color name. defaults to white.
+1
View File
@@ -5,6 +5,7 @@ import { analysers, getAnalyzerData } from 'superdough';
/** /**
* Renders a spectrum analyzer for the incoming audio signal. * Renders a spectrum analyzer for the incoming audio signal.
* @name spectrum * @name spectrum
* @tags visualization
* @param {object} config optional config with options: * @param {object} config optional config with options:
* @param {integer} thickness line thickness in px (default 3) * @param {integer} thickness line thickness in px (default 3)
* @param {integer} speed scroll speed (default 1) * @param {integer} speed scroll speed (default 1)
+3 -3
View File
@@ -234,9 +234,6 @@ importers:
packages/core: packages/core:
dependencies: dependencies:
'@kabelsalat/web':
specifier: ^0.4.1
version: 0.4.1
fraction.js: fraction.js:
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.2.1 version: 5.2.1
@@ -524,6 +521,9 @@ importers:
'@kabelsalat/lib': '@kabelsalat/lib':
specifier: ^0.4.1 specifier: ^0.4.1
version: 0.4.1 version: 0.4.1
'@kabelsalat/web':
specifier: ^0.4.1
version: 0.4.1
nanostores: nanostores:
specifier: ^0.11.3 specifier: ^0.11.3
version: 0.11.3 version: 0.11.3
+29
View File
@@ -10874,6 +10874,35 @@ exports[`runs examples > example "seqPLoop" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "setDefaultJoin" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:saw velocity:1 note:F delay:0 ]",
"[ 1/4 → 1/3 | s:saw velocity:1 note:A delay:0 ]",
"[ 1/3 → 1/2 | s:saw velocity:1 note:A delay:0.2 ]",
"[ 1/2 → 2/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
"[ 2/3 → 3/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
"[ 3/4 → 1/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
"[ 1/1 → 5/4 | s:saw velocity:1 note:F delay:0 ]",
"[ 5/4 → 4/3 | s:saw velocity:1 note:A delay:0 ]",
"[ 4/3 → 3/2 | s:saw velocity:1 note:A delay:0.2 ]",
"[ 3/2 → 5/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
"[ 5/3 → 7/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
"[ 7/4 → 2/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
"[ 2/1 → 9/4 | s:saw velocity:1 note:F delay:0 ]",
"[ 9/4 → 7/3 | s:saw velocity:1 note:A delay:0 ]",
"[ 7/3 → 5/2 | s:saw velocity:1 note:A delay:0.2 ]",
"[ 5/2 → 8/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
"[ 8/3 → 11/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
"[ 11/4 → 3/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
"[ 3/1 → 13/4 | s:saw velocity:1 note:F delay:0 ]",
"[ 13/4 → 10/3 | s:saw velocity:1 note:A delay:0 ]",
"[ 10/3 → 7/2 | s:saw velocity:1 note:A delay:0.2 ]",
"[ 7/2 → 11/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
"[ 11/3 → 15/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
"[ 15/4 → 4/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
]
`;
exports[`runs examples > example "setGainCurve" example index 0 1`] = ` exports[`runs examples > example "setGainCurve" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | s:bd gain:0.5 ]", "[ 0/1 → 1/4 | s:bd gain:0.5 ]",
+2
View File
@@ -1,7 +1,9 @@
import { afterEach } from 'vitest'; import { afterEach } from 'vitest';
import { useRNG } from './packages/core/signal.mjs'; import { useRNG } from './packages/core/signal.mjs';
import { setDefaultJoin } from './packages/core/pattern.mjs';
afterEach(() => { afterEach(() => {
// Avoid bleed between tests // Avoid bleed between tests
useRNG('legacy'); useRNG('legacy');
setDefaultJoin('in');
}); });
+1
View File
@@ -33,6 +33,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
<!-- the following variables are just a fallback to make sure everything is readable without JS --> <!-- the following variables are just a fallback to make sure everything is readable without JS -->
<style is:global> <style is:global>
:root { :root {
color-scheme: dark;
--background: #222; --background: #222;
--lineBackground: #22222299; --lineBackground: #22222299;
--foreground: #fff; --foreground: #fff;
@@ -32,6 +32,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
<!-- the following variables are just a fallback to make sure everything is readable without JS --> <!-- the following variables are just a fallback to make sure everything is readable without JS -->
<style is:global> <style is:global>
:root { :root {
color-scheme: dark;
--background: #222; --background: #222;
--lineBackground: #22222299; --lineBackground: #22222299;
--foreground: #fff; --foreground: #fff;
-12
View File
@@ -1,15 +1,3 @@
:root {
--background: #222;
--lineBackground: #22222299;
--foreground: #fff;
--caret: #ffcc00;
--selection: rgba(128, 203, 196, 0.5);
--selectionMatch: #036dd626;
--lineHighlight: #00000050;
--gutterBackground: transparent;
--gutterForeground: #8a919966;
}
.darken::before { .darken::before {
content: ' '; content: ' ';
position: fixed; position: fixed;
+1 -1
View File
@@ -8,7 +8,7 @@ export function Code(Props) {
return ( return (
<section <section
className={'text-gray-100 cursor-text pb-0 overflow-auto grow'} className={'text-gray-100 cursor-text pb-0 overflow-auto grow z-10'}
id="code" id="code"
ref={(el) => { ref={(el) => {
containerRef.current = el; containerRef.current = el;
@@ -2,7 +2,7 @@ import Loader from '@src/repl/components/Loader';
import { Code } from '@src/repl/components/Code'; import { Code } from '@src/repl/components/Code';
import BigPlayButton from '@src/repl/components/BigPlayButton'; import BigPlayButton from '@src/repl/components/BigPlayButton';
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
import { Header } from './Header'; import { MainPanel } from './panel/Panel';
// type Props = { // type Props = {
// context: replcontext, // context: replcontext,
@@ -14,7 +14,7 @@ export default function EmbeddedReplEditor(Props) {
return ( return (
<div className="h-full flex flex-col relative" {...editorProps}> <div className="h-full flex flex-col relative" {...editorProps}>
<Loader active={pending} /> <Loader active={pending} />
<Header context={context} embedded={true} /> <MainPanel context={context} embedded={true} />
<BigPlayButton started={started} handleTogglePlay={handleTogglePlay} /> <BigPlayButton started={started} handleTogglePlay={handleTogglePlay} />
<div className="grow flex relative overflow-hidden"> <div className="grow flex relative overflow-hidden">
<Code containerRef={containerRef} editorRef={editorRef} init={init} /> <Code containerRef={containerRef} editorRef={editorRef} init={init} />
-150
View File
@@ -1,150 +0,0 @@
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
import cx from '@src/cx.mjs';
import { useSettings, setIsZen } from '../../settings.mjs';
import '../Repl.css';
const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
export function Header({ context, embedded = false }) {
const { started, pending, isDirty, activeCode, handleTogglePlay, handleEvaluate, handleShuffle, handleShare } =
context;
const isEmbedded = typeof window !== 'undefined' && (embedded || window.location !== window.parent.location);
const { isZen, isButtonRowHidden, isCSSAnimationDisabled, fontFamily } = useSettings();
return (
<header
id="header"
className={cx(
'flex-none text-black z-[100] text-lg select-none h-20 md:h-14',
!isZen && !isEmbedded && 'bg-lineHighlight',
isZen ? 'h-12 w-8 fixed top-0 left-0' : 'sticky top-0 w-full py-1 justify-between',
isEmbedded ? 'flex' : 'md:flex',
)}
style={{ fontFamily }}
>
<div className="px-4 flex space-x-2 md:pt-0 select-none">
<h1
onClick={() => {
if (isEmbedded) window.open(window.location.href.replace('embed', ''));
}}
className={cx(
isEmbedded ? 'text-l cursor-pointer' : 'text-xl',
'text-foreground font-bold flex space-x-2 items-center',
)}
>
<div
className={cx(
'mt-[1px]',
started && !isCSSAnimationDisabled && 'animate-spin',
'cursor-pointer text-blue-500',
isZen && 'fixed top-2 right-4',
)}
onClick={() => {
if (!isEmbedded) {
setIsZen(!isZen);
}
}}
>
<span className="block text-foreground rotate-90"></span>
</div>
{!isZen && (
<div className="space-x-2">
<span className="">strudel</span>
<span className="text-sm font-medium">REPL</span>
{!isEmbedded && isButtonRowHidden && (
<a href={`${baseNoTrailing}/learn`} className="text-sm opacity-25 font-medium">
DOCS
</a>
)}
</div>
)}
</h1>
</div>
{!isZen && !isButtonRowHidden && (
<div className="flex max-w-full overflow-auto text-foreground px-1 md:px-2">
<button
onClick={handleTogglePlay}
title={started ? 'stop' : 'play'}
className={cx(
!isEmbedded ? 'p-2' : 'px-2',
'hover:opacity-50',
!started && !isCSSAnimationDisabled && 'animate-pulse',
)}
>
{!pending ? (
<span className={cx('flex items-center space-x-2')}>
{started ? <StopCircleIcon className="w-6 h-6" /> : <PlayCircleIcon className="w-6 h-6" />}
{!isEmbedded && <span>{started ? 'stop' : 'play'}</span>}
</span>
) : (
<>loading...</>
)}
</button>
<button
onClick={handleEvaluate}
title="update"
className={cx(
'flex items-center space-x-1',
!isEmbedded ? 'p-2' : 'px-2',
!isDirty || !activeCode ? 'opacity-50' : 'hover:opacity-50',
)}
>
{!isEmbedded && <span>update</span>}
</button>
{/* !isEmbedded && (
<button
title="shuffle"
className="hover:opacity-50 p-2 flex items-center space-x-1"
onClick={handleShuffle}
>
<span> shuffle</span>
</button>
) */}
{!isEmbedded && (
<button
title="share"
className={cx(
'cursor-pointer hover:opacity-50 flex items-center space-x-1',
!isEmbedded ? 'p-2' : 'px-2',
)}
onClick={handleShare}
>
<span>share</span>
</button>
)}
{!isEmbedded && (
<a
title="learn"
href={`${baseNoTrailing}/workshop/getting-started/`}
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
>
<span>learn</span>
</a>
)}
{/* {isEmbedded && (
<button className={cx('hover:opacity-50 px-2')}>
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
🚀
</a>
</button>
)}
{isEmbedded && (
<button className={cx('hover:opacity-50 px-2')}>
<a
onClick={() => {
window.location.href = initialUrl;
window.location.reload();
}}
title="Reset"
>
💔
</a>
</button>
)} */}
</div>
)}
</header>
);
}
+11 -7
View File
@@ -1,8 +1,7 @@
import Loader from '@src/repl/components/Loader';
import { HorizontalPanel, VerticalPanel } from '@src/repl/components/panel/Panel';
import { Code } from '@src/repl/components/Code'; import { Code } from '@src/repl/components/Code';
import Loader from '@src/repl/components/Loader';
import { HorizontalPanel, MainPanel, VerticalPanel } from '@src/repl/components/panel/Panel';
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
import { Header } from './Header';
import { useSettings } from '@src/settings.mjs'; import { useSettings } from '@src/settings.mjs';
// type Props = { // type Props = {
@@ -14,17 +13,22 @@ export default function ReplEditor(Props) {
const { containerRef, editorRef, error, init, pending } = context; const { containerRef, editorRef, error, init, pending } = context;
const settings = useSettings(); const settings = useSettings();
const { panelPosition, isZen } = settings; const { panelPosition, isZen } = settings;
const isEmbedded = typeof window !== 'undefined' && window.location !== window.parent.location;
return ( return (
<div className="h-full flex flex-col relative" {...editorProps}> <div className="h-full flex flex-col relative" {...editorProps}>
<Loader active={pending} /> <Loader active={pending} />
<Header context={context} /> <div className="flex flex-col grow overflow-hidden">
<div className="grow flex relative overflow-hidden"> {/* <MainPanel context={context} isEmbedded={isEmbedded} className="hidden sm:block" /> */}
<Code containerRef={containerRef} editorRef={editorRef} init={init} /> <MainPanel context={context} isEmbedded={isEmbedded} />
{!isZen && panelPosition === 'right' && <VerticalPanel context={context} />} <div className="flex overflow-hidden">
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
{!isZen && panelPosition === 'right' && <VerticalPanel context={context} />}
</div>
</div> </div>
<UserFacingErrorMessage error={error} /> <UserFacingErrorMessage error={error} />
{!isZen && panelPosition === 'bottom' && <HorizontalPanel context={context} />} {!isZen && panelPosition === 'bottom' && <HorizontalPanel context={context} />}
{/* <MainPanel context={context} isEmbedded={isEmbedded} className="block sm:hidden" /> */}
</div> </div>
); );
} }
@@ -2,7 +2,7 @@ import cx from '@src/cx.mjs';
export function ActionButton({ children, label, labelIsHidden, className, ...buttonProps }) { export function ActionButton({ children, label, labelIsHidden, className, ...buttonProps }) {
return ( return (
<button className={cx('hover:opacity-50 text-nowrap w-fit', className)} title={label} {...buttonProps}> <button className={cx('hover:opacity-50 text-xs text-nowrap w-fit', className)} title={label} {...buttonProps}>
{labelIsHidden !== true && label} {labelIsHidden !== true && label}
{children} {children}
</button> </button>
@@ -13,16 +13,13 @@ export function SpecialActionButton(props) {
const { className, ...buttonProps } = props; const { className, ...buttonProps } = props;
return ( return (
<ActionButton <ActionButton {...buttonProps} className={cx('bg-background p-2 max-w-[300px] hover:opacity-50', className)} />
{...buttonProps}
className={cx('bg-background p-2 max-w-[300px] rounded-md hover:opacity-50', className)}
/>
); );
} }
export function ActionInput({ label, className, ...props }) { export function ActionInput({ label, className, ...props }) {
return ( return (
<label className={cx('inline-flex items-center cursor-pointer', className)}> <label className={cx('inline-flex items-center cursor-pointer ', className)}>
<input {...props} className="sr-only peer" /> <input {...props} className="sr-only peer" />
<span className="inline-flex items-center peer-hover:opacity-50">{label}</span> <span className="inline-flex items-center peer-hover:opacity-50">{label}</span>
@@ -35,7 +32,7 @@ export function SpecialActionInput({ className, ...props }) {
<ActionInput <ActionInput
{...props} {...props}
className={className} className={className}
label={<span className="bg-background p-2 max-w-[300px] rounded-md">{props.label}</span>} label={<span className="bg-background p-2 max-w-[300px]">{props.label}</span>}
/> />
); );
} }
@@ -0,0 +1,19 @@
export function SidebarIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-panel-right-icon lucide-panel-right"
>
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M15 3v18" />
</svg>
);
}
@@ -0,0 +1,13 @@
// Source: https://commons.wikimedia.org/wiki/File:CHAM_PUNCTUATION_SPIRAL.svg
// License: SIL Open Font License, Version 1.1. The license text is available at https://openfontlicense.org/open-font-license-official-text/
export function StrudelIcon({ className = '' }) {
return (
<svg className={className} width="100%" height="100%" viewBox="0 0 213 213">
<g>
<g id="_1" serif:id="1">
<path d="M199.905,102.467c-0,17.959 -3.695,33.45 -11.078,46.468c-7.369,13.024 -17.9,23.079 -31.586,30.173c-13.673,7.093 -29.994,10.64 -48.961,10.64c-19.964,0 -37.183,-3.874 -51.66,-11.617c-14.482,-7.749 -25.592,-18.775 -33.334,-33.103c-7.749,-14.322 -11.617,-31.407 -11.617,-51.248l17.143,-0c-0,16.95 3.187,31.503 9.561,43.666c6.373,12.15 15.485,21.48 27.346,27.989c11.874,6.509 26.061,9.766 42.561,9.766c15.485,0 28.798,-2.898 39.94,-8.687c11.154,-5.789 19.693,-14.11 25.624,-24.956c5.943,-10.858 8.918,-23.812 8.918,-38.86c-0,-20.85 -5.5,-37.202 -16.5,-49.063c-11,-11.874 -26.344,-17.811 -46.031,-17.811c-17.515,-0 -31.266,4.883 -41.251,14.649c-9.991,9.767 -14.983,23.337 -14.983,40.711c-0,15.781 4.015,28.13 12.054,37.035c8.031,8.893 19.07,13.339 33.103,13.339c12.452,0 22.186,-3.431 29.196,-10.306c7.023,-6.888 10.538,-16.333 10.538,-28.348c-0,-10.281 -2.57,-18.415 -7.711,-24.416c-5.14,-6.014 -12.413,-9.021 -21.82,-9.021c-7.813,-0 -13.93,2.178 -18.351,6.528c-4.407,4.337 -6.605,10.197 -6.605,17.579c0,1.44 0.064,2.815 0.206,4.112c4.485,-7.363 11.141,-11.051 19.97,-11.051c5.5,-0 9.875,1.561 13.133,4.678c3.27,3.103 4.909,7.324 4.909,12.67c-0,5.796 -1.966,10.506 -5.886,14.136c-3.906,3.617 -9.265,5.423 -16.063,5.423c-8.533,-0 -15.337,-2.814 -20.407,-8.456c-5.076,-5.654 -7.607,-13.114 -7.607,-22.386c-0,-11.135 3.431,-19.957 10.306,-26.472c6.888,-6.528 16.121,-9.792 27.706,-9.792c13.467,0 24.03,4.164 31.689,12.491c7.672,8.327 11.515,19.584 11.515,33.771c-0,16.057 -4.884,28.753 -14.65,38.089c-9.767,9.343 -23.195,14.008 -40.274,14.008c-12.594,-0 -23.491,-2.603 -32.692,-7.814c-9.188,-5.204 -16.243,-12.625 -21.178,-22.257c-4.921,-9.625 -7.376,-21.023 -7.376,-34.182c0,-14.342 2.93,-26.64 8.79,-36.907c5.873,-10.281 14.271,-18.203 25.187,-23.774c10.93,-5.584 23.928,-8.379 38.989,-8.379c16.359,0 30.469,3.258 42.33,9.767c11.874,6.515 20.985,15.819 27.346,27.911c6.374,12.08 9.561,26.518 9.561,43.307Zm-86.408,-6.939c-7.235,-0 -11.867,4.491 -13.904,13.467c2.756,3.033 6.528,4.549 11.308,4.549c7.659,0 11.489,-3.11 11.489,-9.329c-0,-2.757 -0.797,-4.89 -2.39,-6.4c-1.581,-1.523 -3.746,-2.287 -6.503,-2.287Z" />
</g>
</g>
</svg>
);
}
@@ -1,4 +1,4 @@
import { Textbox } from '../textbox/Textbox'; import { Textbox } from '@src/repl/components/panel/SettingsTab';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
function IncButton({ children, className, ...buttonProps }) { function IncButton({ children, className, ...buttonProps }) {
@@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { SelectInput } from './SelectInput'; import { SelectInputDuplicate } from '@src/repl/components/panel/SettingsTab';
import { getAudioDevices } from '@strudel/webaudio'; import { getAudioDevices } from '@strudel/webaudio';
const initdevices = new Map(); const initdevices = new Map();
@@ -29,7 +29,7 @@ export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) {
options.set(deviceName, deviceName); options.set(deviceName, deviceName);
}); });
return ( return (
<SelectInput <SelectInputDuplicate
isDisabled={isDisabled} isDisabled={isDisabled}
options={options} options={options}
onClick={onClick} onClick={onClick}
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { audioEngineTargets } from '../../../settings.mjs'; import { audioEngineTargets } from '../../../settings.mjs';
import { SelectInput } from './SelectInput'; import { SelectInputDuplicate } from '@src/repl/components/panel/SettingsTab';
// Allows the user to select an audio interface for Strudel to play through // Allows the user to select an audio interface for Strudel to play through
export function AudioEngineTargetSelector({ target, onChange, isDisabled }) { export function AudioEngineTargetSelector({ target, onChange, isDisabled }) {
@@ -12,8 +12,8 @@ export function AudioEngineTargetSelector({ target, onChange, isDisabled }) {
[audioEngineTargets.osc, audioEngineTargets.osc], [audioEngineTargets.osc, audioEngineTargets.osc],
]); ]);
return ( return (
<div className=" flex flex-col gap-1"> <div className="flex flex-col gap-1">
<SelectInput isDisabled={isDisabled} options={options} value={target} onChange={onTargetChange} /> <SelectInputDuplicate isDisabled={isDisabled} options={options} value={target} onChange={onTargetChange} />
{target === audioEngineTargets.osc && ( {target === audioEngineTargets.osc && (
<div> <div>
<p className="text-sm italic"> <p className="text-sm italic">
@@ -7,8 +7,10 @@ export function ConsoleTab() {
const log = useStore($strudel_log_history); const log = useStore($strudel_log_history);
const { fontFamily } = useSettings(); const { fontFamily } = useSettings();
return ( return (
<div id="console-tab" className="break-all w-full first-line:text-sm p-2 h-full" style={{ fontFamily }}> <div id="console-tab" className="break-all w-full h-full" style={{ fontFamily }}>
<div className="bg-background h-full w-full overflow-auto space-y-1 p-2 rounded-md"> <div className="h-full w-full overflow-auto space-y-1 p-2 rounded-md">
{' '}
{/* bg-background */}
{log.map((l, i) => { {log.map((l, i) => {
const message = linkify(l.message); const message = linkify(l.message);
const color = l.data?.hap?.value?.color; const color = l.data?.hap?.value?.color;
@@ -1,10 +1,7 @@
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import NumberInput from '@src/repl/components/NumberInput'; import { useState } from 'react';
import { useEffect, useState } from 'react'; import { Textbox } from '@src/repl/components/panel/SettingsTab';
import { Textbox } from '../textbox/Textbox';
import { getAudioContext } from '@strudel/webaudio'; import { getAudioContext } from '@strudel/webaudio';
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
function Checkbox({ label, value, onChange, disabled = false }) { function Checkbox({ label, value, onChange, disabled = false }) {
return ( return (
@@ -48,7 +45,7 @@ export default function ExportTab(Props) {
return ( return (
<> <>
<div className="text-foreground w-full p-4 space-y-4"> <div className="text-foreground w-full space-y-4 p-4">
<FormItem label="File name" disabled={exporting}> <FormItem label="File name" disabled={exporting}>
<Textbox <Textbox
onBlur={(e) => { onBlur={(e) => {
@@ -59,7 +56,7 @@ export default function ExportTab(Props) {
}} }}
disabled={exporting} disabled={exporting}
placeholder="Leave empty to use current date" placeholder="Leave empty to use current date"
className={cx('placeholder:opacity-50', exporting && 'opacity-50 border-opacity-50')} className={cx('placeholder-muted', exporting && 'opacity-50 border-opacity-50')}
value={downloadName ?? ''} value={downloadName ?? ''}
/> />
</FormItem> </FormItem>
+3 -3
View File
@@ -1,15 +1,15 @@
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
export function ButtonGroup({ value, onChange, items }) { export function ButtonGroup({ value, onChange, items, wrap = false }) {
return ( return (
<div className="flex max-w-lg"> <div className={cx('flex max-w-lg space-x-0 text-xs', wrap && 'flex-wrap')}>
{Object.entries(items).map(([key, label], i, arr) => ( {Object.entries(items).map(([key, label], i, arr) => (
<button <button
key={key} key={key}
id={key} id={key}
onClick={() => onChange(key)} onClick={() => onChange(key)}
className={cx( className={cx(
'px-2 border-b h-8 whitespace-nowrap', 'px-2 border-b-2 h-8 whitespace-nowrap border-box max-h-8 hover:opacity-50',
// i === 0 && 'rounded-l-md', // i === 0 && 'rounded-l-md',
// i === arr.length - 1 && 'rounded-r-md', // i === arr.length - 1 && 'rounded-r-md',
// value === key ? 'bg-background' : 'bg-lineHighlight', // value === key ? 'bg-background' : 'bg-lineHighlight',
+226 -109
View File
@@ -1,40 +1,194 @@
import { Bars3Icon, PlayIcon, StopIcon, XMarkIcon } from '@heroicons/react/16/solid';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import { setPanelPinned, setActiveFooter as setTab, setIsPanelOpened, useSettings } from '../../../settings.mjs'; import { StrudelIcon } from '@src/repl/components/icons/StrudelIcon';
import { useSettings, setIsZen, setIsPanelOpened, setActiveFooter as setTab } from '../../../settings.mjs';
import '../../Repl.css';
import { useLogger } from '../useLogger';
import { ConsoleTab } from './ConsoleTab'; import { ConsoleTab } from './ConsoleTab';
import ExportTab from './ExportTab';
import { FilesTab } from './FilesTab'; import { FilesTab } from './FilesTab';
import { PatternsTab } from './PatternsTab';
import { Reference } from './Reference'; import { Reference } from './Reference';
import { SettingsTab } from './SettingsTab'; import { SettingsTab } from './SettingsTab';
import { SoundsTab } from './SoundsTab'; import { SoundsTab } from './SoundsTab';
import { useLogger } from '../useLogger';
import { WelcomeTab } from './WelcomeTab'; import { WelcomeTab } from './WelcomeTab';
import { PatternsTab } from './PatternsTab';
import { ChevronLeftIcon, XMarkIcon } from '@heroicons/react/16/solid';
import ExportTab from './ExportTab';
const TAURI = typeof window !== 'undefined' && window.__TAURI__; const TAURI = typeof window !== 'undefined' && window.__TAURI__;
export function HorizontalPanel({ context }) { const { BASE_URL } = import.meta.env;
const settings = useSettings(); const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
const { isPanelOpen, activeFooter: tab } = settings;
export function LogoButton({ context, isEmbedded }) {
const { started } = context;
const { isZen, isCSSAnimationDisabled, fontFamily } = useSettings();
return (
<div
className={cx(
'mt-[1px]',
started && !isCSSAnimationDisabled && 'animate-spin',
'cursor-pointer text-blue-500',
isZen && 'fixed top-2 right-4',
)}
onClick={() => {
if (!isEmbedded) {
setIsZen(!isZen);
}
}}
>
<span className="block text-foreground rotate-90">
<StrudelIcon className="w-5 h-5 fill-foreground" />
</span>
</div>
);
}
export function MainPanel({ context, isEmbedded = false, className }) {
const { isZen, isButtonRowHidden, fontFamily } = useSettings();
return (
<nav
id="header"
className={cx(
//'border-t sm:border-b sm:border-t-0 border-muted',
'border-b border-muted',
'flex-none text-black z-[100] text-sm select-none min-h-10 max-h-10',
!isZen && !isEmbedded && 'bg-lineHighlight',
// isZen ? 'h-12 w-8 fixed top-0 left-0' : 'h-10 sticky top-0 w-full justify-between',
isZen ? 'h-12 w-8 fixed top-0 left-0' : '',
'flex items-center',
className,
)}
style={{ fontFamily }}
>
<div className={cx('flex w-full justify-between')}>
{' '}
{/*flex-wrap*/}
<div className="px-3 py-1 flex space-x-2 select-none">
<h1
onClick={() => {
if (isEmbedded) window.open(window.location.href.replace('embed', ''));
}}
className={cx(
isEmbedded ? 'text-l cursor-pointer' : 'text-xl',
'text-foreground font-bold flex space-x-2 items-center',
)}
>
<LogoButton context={context} isEmbedded={isEmbedded} />
{!isZen && (
<div className="space-x-2 flex items-baseline">
<span className="hidden sm:block">strudel</span>
<span className="text-sm font-medium hidden sm:block">REPL</span>
{/* !isEmbedded && isButtonRowHidden && (
<a href={`${baseNoTrailing}/learn`} className="text-sm opacity-25 font-medium">
DOCS
</a>
) */}
</div>
)}
</h1>
</div>
{!isZen && (
<div className="flex grow justify-end">
{!isButtonRowHidden && <MainMenu isEmbedded={isEmbedded} context={context} />}
{/* className="hidden sm:flex" */}
<PanelToggle isEmbedded={isEmbedded} isZen={isZen} />
</div>
)}
</div>
</nav>
);
}
export function Footer({ context, isEmbedded = false }) {
return (
<div className="border-t border-muted bg-lineHighlight block lg:hidden">
{/* block lg:hidden */}
<MainMenu context={context} isEmbedded={isEmbedded} />
</div>
);
}
function MainMenu({ context, isEmbedded = false, className }) {
const { started, pending, isDirty, activeCode, handleTogglePlay, handleEvaluate, handleShare } = context;
const { isCSSAnimationDisabled } = useSettings();
return (
<div className={cx('flex text-sm max-w-full shrink-0 overflow-hidden text-foreground px-2 h-10', className)}>
<button
onClick={handleTogglePlay}
title={started ? 'stop' : 'play'}
className={cx('px-2 hover:opacity-50', !started && !isCSSAnimationDisabled && 'animate-pulse')}
>
{/* {!pending ? ( */}
<span className={cx('flex items-center space-x-2')}>
{/* {started ? <StopCircleIcon className="w-5 h-5" /> : <PlayCircleIcon className="w-5 h-5" />} */}
{started ? <StopIcon className="w-5 h-5" /> : <PlayIcon className="w-5 h-5" />}
{!isEmbedded && <span>{started ? 'stop' : 'play'}</span>}
</span>
{/* ) : (
<>loading...</>
)} */}
</button>
<button
onClick={handleEvaluate}
title="update"
className={cx('flex items-center space-x-1 px-2', !isDirty || !activeCode ? 'opacity-50' : 'hover:opacity-50')}
>
{!isEmbedded && <span>update</span>}
</button>
{!isEmbedded && (
<button
title="share"
className={cx('cursor-pointer hover:opacity-50 flex items-center space-x-1 px-2')}
onClick={handleShare}
>
<span>share</span>
</button>
)}
{!isEmbedded && (
<a
title="learn"
href={`${baseNoTrailing}/workshop/getting-started/`}
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
>
<span>learn</span>
</a>
)}
</div>
);
}
function PanelCloseButton() {
const { isPanelOpen } = useSettings();
return (
isPanelOpen && (
<button
onClick={() => setIsPanelOpened(false)}
className={cx('border-l border-muted px-2 py-0 text-foreground hover:opacity-50')}
aria-label="Close Menu"
>
<XMarkIcon className="w-6 h-6" />
</button>
)
);
}
export function HorizontalPanel({ context }) {
const { isPanelOpen, activeFooter: tab } = useSettings();
return ( return (
<PanelNav <PanelNav
settings={settings} className={cx(
className={cx(isPanelOpen ? `min-h-[360px] max-h-[360px]` : 'min-h-12 max-h-12', 'overflow-hidden flex flex-col')} isPanelOpen ? `min-h-[360px] max-h-[360px]` : 'min-h-10 max-h-10',
'overflow-hidden flex flex-col relative',
)}
> >
<div className="flex justify-between min-h-10 max-h-10 grid-cols-2 items-center border-t border-muted">
<PanelCloseButton />
<Tabs setTab={setTab} tab={tab} />
</div>
{isPanelOpen && ( {isPanelOpen && (
<div className="flex h-full overflow-auto pr-10 "> <div className="w-full h-full overflow-auto border-t border-muted">
<PanelContent context={context} tab={tab} /> <PanelContent context={context} tab={tab} />
</div> </div>
)} )}
<div className="absolute right-4 pt-4">
<PanelActionButton settings={settings} />
</div>
<div className="flex justify-between min-h-12 max-h-12 grid-cols-2 items-center">
<Tabs setTab={setTab} tab={tab} />
</div>
</PanelNav> </PanelNav>
); );
} }
@@ -42,36 +196,34 @@ export function HorizontalPanel({ context }) {
export function VerticalPanel({ context }) { export function VerticalPanel({ context }) {
const settings = useSettings(); const settings = useSettings();
const { activeFooter: tab, isPanelOpen } = settings; const { activeFooter: tab, isPanelOpen } = settings;
if (!isPanelOpen) {
return;
}
return ( return (
<PanelNav <PanelNav
settings={settings} settings={settings}
className={cx(isPanelOpen ? `min-w-[min(600px,80vw)] max-w-[min(600px,80vw)]` : 'min-w-12 max-w-12')} className={cx(
> //'border-l border-muted shrink-0',
{isPanelOpen ? ( 'border-0 border-muted shrink-0 h-full overflow-hidden',
<div className={cx('flex flex-col h-full')}> isPanelOpen
<div className="flex justify-between w-full "> ? //? `min-w-full max-w-full lg:min-w-[min(600px,100vw)] lg:max-w-[min(600px,80vw)]`
<Tabs setTab={setTab} tab={tab} /> `min-w-[min(600px,100vw)] max-w-[min(600px,80vw)]`
<PanelActionButton settings={settings} /> : 'min-w-12 max-w-12',
</div>
<div className="overflow-auto h-full">
<PanelContent context={context} tab={tab} />
</div>
</div>
) : (
<button
onClick={(e) => {
setIsPanelOpened(true);
}}
aria-label="open menu panel"
className={cx(
'flex flex-col hover:bg-lineBackground items-center cursor-pointer justify-center w-full h-full',
)}
>
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
</button>
)} )}
>
<div className={cx('flex flex-col h-full')}>
<div className="flex justify-between w-full overflow-hidden border-b border-muted min-h-10 max-h-10">
{/* <div className="block sm:hidden text-foreground px-3 py-2 border-l border-muted">
<LogoButton context={context} />
</div> */}
<PanelCloseButton />
<Tabs setTab={setTab} tab={tab} />
{/* <PanelCloseButton /> */}
</div>
<div className="overflow-auto h-full border-l border-muted">
<PanelContent context={context} tab={tab} />
</div>
</div>
</PanelNav> </PanelNav>
); );
} }
@@ -89,8 +241,8 @@ if (TAURI) {
tabNames.files = 'files'; tabNames.files = 'files';
} }
function PanelNav({ children, className, settings, ...props }) { function PanelNav({ children, className, ...props }) {
const isHoverBehavior = settings.togglePanelTrigger === 'hover'; const settings = useSettings();
return ( return (
<nav <nav
onClick={() => { onClick={() => {
@@ -98,18 +250,8 @@ function PanelNav({ children, className, settings, ...props }) {
setIsPanelOpened(true); setIsPanelOpened(true);
} }
}} }}
onMouseEnter={() => {
if (isHoverBehavior && !settings.isPanelOpen) {
setIsPanelOpened(true);
}
}}
onMouseLeave={() => {
if (isHoverBehavior && !settings.isPanelPinned) {
setIsPanelOpened(false);
}
}}
aria-label="Menu Panel" aria-label="Menu Panel"
className={cx('bg-lineHighlight group overflow-x-auto', className)} className={cx('h-full bg-lineHighlight group overflow-x-auto', className)}
{...props} {...props}
> >
{children} {children}
@@ -145,7 +287,7 @@ function PanelTab({ label, isSelected, onClick }) {
<button <button
onClick={onClick} onClick={onClick}
className={cx( className={cx(
'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b', 'h-10 px-2 text-sm border-t-2 border-t-transparent text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b-2',
isSelected ? 'border-foreground' : 'border-transparent', isSelected ? 'border-foreground' : 'border-transparent',
)} )}
> >
@@ -154,65 +296,40 @@ function PanelTab({ label, isSelected, onClick }) {
</> </>
); );
} }
function Tabs({ setTab, tab, className }) { function Tabs({ className }) {
const { isPanelOpen, activeFooter: tab } = useSettings();
return ( return (
<div className={cx('flex select-none max-w-full overflow-auto pb-2', className)}> <div
className={cx(
'px-2 border-l border-muted w-full flex select-none max-w-full h-10 max-h-10 min-h-10 overflow-auto items-center',
className,
)}
>
{Object.keys(tabNames).map((key) => { {Object.keys(tabNames).map((key) => {
const val = tabNames[key]; const val = tabNames[key];
return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />; return <PanelTab key={key} isSelected={tab === val && isPanelOpen} label={key} onClick={() => setTab(val)} />;
})} })}
</div> </div>
); );
} }
function PanelActionButton({ settings }) { export function PanelToggle({ isEmbedded, isZen }) {
const { togglePanelTrigger, isPanelPinned, isPanelOpen } = settings; const { panelPosition, isPanelOpen } = useSettings();
const isHoverBehavior = togglePanelTrigger === 'hover';
if (!isPanelOpen) {
return;
}
if (isHoverBehavior) {
return <PinButton pinned={isPanelPinned} />;
}
return <CloseButton onClick={() => setIsPanelOpened(false)} />;
}
function PinButton({ pinned }) {
return ( return (
<button !isEmbedded &&
onClick={() => setPanelPinned(!pinned)} !isZen &&
className={cx( panelPosition === 'right' && (
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex', <button
pinned ? 'flex' : 'hidden', title="share"
)} className={cx(
aria-label="Pin Menu Panel" 'border-l border-muted px-2 py-0 text-foreground hover:opacity-50' /* , isPanelOpen && 'hidden' */,
> )}
<svg onClick={() => setIsPanelOpened(!isPanelOpen)}
stroke="currentColor"
fill={'currentColor'}
strokeWidth="0"
className="w-full h-full"
opacity={pinned ? 1 : '.3'}
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
> >
<path d="M9.828.722a.5.5 0 0 1 .354.146l4.95 4.95a.5.5 0 0 1 0 .707c-.48.48-1.072.588-1.503.588-.177 0-.335-.018-.46-.039l-3.134 3.134a6 6 0 0 1 .16 1.013c.046.702-.032 1.687-.72 2.375a.5.5 0 0 1-.707 0l-2.829-2.828-3.182 3.182c-.195.195-1.219.902-1.414.707s.512-1.22.707-1.414l3.182-3.182-2.828-2.829a.5.5 0 0 1 0-.707c.688-.688 1.673-.767 2.375-.72a6 6 0 0 1 1.013.16l3.134-3.133a3 3 0 0 1-.04-.461c0-.43.108-1.022.589-1.503a.5.5 0 0 1 .353-.146"></path> {/* <span>menu</span> */}
</svg> {/* isPanelOpen ? <XMarkIcon className="w-6 h-6" /> : <Bars3Icon className="w-6 h-6" /> */}
</button> <Bars3Icon className="w-6 h-6" />
); </button>
} )
function CloseButton({ onClick }) {
return (
<button
onClick={onClick}
className={cx(
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
)}
aria-label="Close Menu"
>
<XMarkIcon />
</button>
); );
} }
@@ -8,7 +8,7 @@ import {
useViewingPatternData, useViewingPatternData,
userPattern, userPattern,
} from '../../../user_pattern_utils.mjs'; } from '../../../user_pattern_utils.mjs';
import { useMemo } from 'react'; import { useMemo, useRef } from 'react';
import { getMetadata } from '../../../metadata_parser.js'; import { getMetadata } from '../../../metadata_parser.js';
import { useExamplePatterns } from '../../useExamplePatterns.jsx'; import { useExamplePatterns } from '../../useExamplePatterns.jsx';
import { parseJSON, isUdels } from '../../util.mjs'; import { parseJSON, isUdels } from '../../util.mjs';
@@ -18,7 +18,7 @@ import { Pagination } from '../pagination/Pagination.jsx';
import { useState } from 'react'; import { useState } from 'react';
import { useDebounce } from '../usedebounce.jsx'; import { useDebounce } from '../usedebounce.jsx';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import { Textbox } from '../textbox/Textbox.jsx'; import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx';
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) { export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
const meta = useMemo(() => getMetadata(pattern.code), [pattern]); const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
@@ -43,7 +43,7 @@ function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
className={cx( className={cx(
'mr-4 hover:opacity-50 cursor-pointer block', 'mr-4 hover:opacity-50 cursor-pointer block',
showOutline && 'outline outline-1', showOutline && 'outline outline-1',
showHiglight && 'bg-selection', showHiglight && 'ring-selection',
)} )}
onClick={onClick} onClick={onClick}
> >
@@ -53,11 +53,10 @@ function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
} }
function PatternButtons({ patterns, activePattern, onClick, started }) { function PatternButtons({ patterns, activePattern, onClick, started }) {
const viewingPatternStore = useViewingPatternData(); const viewingPatternData = useViewingPatternData();
const viewingPatternData = parseJSON(viewingPatternStore);
const viewingPatternID = viewingPatternData.id; const viewingPatternID = viewingPatternData.id;
return ( return (
<div className=""> <div className="p-2">
{Object.values(patterns) {Object.values(patterns)
.reverse() .reverse()
.map((pattern) => { .map((pattern) => {
@@ -83,8 +82,8 @@ const updateCodeWindow = (context, patternData, reset = false) => {
export function PatternsTab({ context }) { export function PatternsTab({ context }) {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const activePattern = useActivePattern(); const activePattern = useActivePattern();
const viewingPatternStore = useViewingPatternData(); const viewingPatternData = useViewingPatternData();
const viewingPatternData = parseJSON(viewingPatternStore);
const { userPatterns, patternAutoStart } = useSettings(); const { userPatterns, patternAutoStart } = useSettings();
const viewingPatternID = viewingPatternData?.id; const viewingPatternID = viewingPatternData?.id;
@@ -121,74 +120,71 @@ export function PatternsTab({ context }) {
); );
}), }),
); );
}, [search, viewingPatternStore]); }, [search, userPatterns]);
const importRef = useRef();
return ( return (
<div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full"> <div className="w-full h-full text-foreground flex flex-col overflow-hidden">
<div className="w-full flex"> <Textbox className="w-full border-0" placeholder="Search..." value={search} onChange={setSearch} />
<Textbox className="w-full" placeholder="Search" value={search} onChange={setSearch} /> <div className="px-2 shrink-0 h-8 space-x-4 flex max-w-full overflow-x-auto border-y border-muted">
<ActionButton
label="new"
onClick={() => {
const { data } = userPattern.createAndAddToDB();
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="duplicate"
onClick={() => {
const { data } = userPattern.duplicate(viewingPatternData);
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="delete"
onClick={() => {
const { data } = userPattern.delete(viewingPatternID);
updateCodeWindow(context, { ...data, collection: userPattern.collection });
}}
/>
<input
ref={importRef}
style={{ display: 'none' }}
type="file"
multiple
accept="text/plain,text/x-markdown,application/json"
onChange={(e) => importPatterns(e.target.files)}
/>
<ActionButton label="import" onClick={() => importRef.current.click()} />
<ActionButton label="export" onClick={exportPatterns} />
<ActionButton
label="delete-all"
onClick={() => {
const { data } = userPattern.clearAll();
updateCodeWindow(context, data);
}}
/>
</div> </div>
<div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 ">
<div className="pr-4 space-x-4 flex max-w-full overflow-x-auto">
<ActionButton
label="new"
onClick={() => {
const { data } = userPattern.createAndAddToDB();
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="duplicate"
onClick={() => {
const { data } = userPattern.duplicate(viewingPatternData);
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="delete"
onClick={() => {
const { data } = userPattern.delete(viewingPatternID);
updateCodeWindow(context, { ...data, collection: userPattern.collection });
}}
/>
<label className="hover:opacity-50 cursor-pointer">
<input
style={{ display: 'none' }}
type="file"
multiple
accept="text/plain,text/x-markdown,application/json"
onChange={(e) => importPatterns(e.target.files)}
/>
import
</label>
<ActionButton label="export" onClick={exportPatterns} />
<ActionButton <div className="overflow-auto">
label="delete-all" {/* bg-background */}
onClick={() => { {/* {patternFilter === patternFilterName.user && ( */}
const { data } = userPattern.clearAll(); <PatternButtons
updateCodeWindow(context, data); onClick={(id) => {
}} updateCodeWindow(context, { ...userPatterns[id], collection: userPattern.collection }, patternAutoStart);
/>
</div>
<div className="overflow-auto h-full bg-background p-2 rounded-md"> if (context.started && activePattern === id) {
{/* {patternFilter === patternFilterName.user && ( */} context.handleEvaluate();
<PatternButtons }
onClick={(id) => { }}
updateCodeWindow(context, { ...userPatterns[id], collection: userPattern.collection }, patternAutoStart); patterns={visiblePatterns}
started={context.started}
if (context.started && activePattern === id) { activePattern={activePattern}
context.handleEvaluate(); viewingPatternID={viewingPatternID}
} />
}} {/* )} */}
patterns={visiblePatterns}
started={context.started}
activePattern={activePattern}
viewingPatternID={viewingPatternID}
/>
{/* )} */}
</div>
</div> </div>
</div> </div>
); );
+153 -31
View File
@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react'; import { memo, useEffect, useMemo, useState, Fragment } from 'react';
import jsdocJson from '../../../../../doc.json'; import jsdocJson from '../../../../../doc.json';
import { Textbox } from '../textbox/Textbox'; import { Textbox } from '@src/repl/components/panel/SettingsTab';
import { settingsMap, useSettings } from '@src/settings.mjs';
const isValid = ({ name, description }) => name && !name.startsWith('_') && !!description; const isValid = ({ name, description }) => name && !name.startsWith('_') && !!description;
@@ -10,8 +11,16 @@ const availableFunctions = (() => {
const functions = []; const functions = [];
for (const doc of jsdocJson.docs) { for (const doc of jsdocJson.docs) {
if (!isValid(doc)) continue; if (!isValid(doc)) continue;
if (seen.has(doc.name)) continue;
// jsdoc also uses "tags" for when you use @something in the comments and it doesn't know what
// @something is. We only want data from comments like `@tags fx, superdough` here.
// If nothing is specified, we default to "untagged" for debugging
doc.tags = doc.tags?.filter((t) => t && typeof t === 'string') || ['untagged'];
functions.push(doc); functions.push(doc);
const synonyms = doc.synonyms || []; const synonyms = doc.synonyms || [];
seen.add(doc.name);
for (const s of synonyms) { for (const s of synonyms) {
if (!s || seen.has(s)) continue; if (!s || seen.has(s)) continue;
seen.add(s); seen.add(s);
@@ -29,70 +38,182 @@ const availableFunctions = (() => {
return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name)); return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
})(); })();
const tagCounts = {};
// const tagOptions = { all: `all (${availableFunctions.length})` };
const tagOptions = { all: `all` };
for (const doc of availableFunctions) {
(doc.tags || ['untagged']).forEach((t) => {
if (typeof t === 'string' && t) {
tagCounts[t] = (tagCounts[t] || 0) + 1;
//tagOptions[t] = `${t} (${tagCounts[t]})`;
tagOptions[t] = t;
}
});
}
const getInnerText = (html) => { const getInnerText = (html) => {
var div = document.createElement('div'); var div = document.createElement('div');
div.innerHTML = html; div.innerHTML = html;
return div.textContent || div.innerText || ''; return div.textContent || div.innerText || '';
}; };
export function Reference() { export const Reference = memo(function Reference() {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [selectedFunction, setSelectedFunction] = useState(null);
const { referenceTag } = useSettings();
const visibleFunctions = useMemo(() => { const toggleTag = (tag) => {
if (referenceTag === tag) {
setReferenceTag('all');
} else {
setReferenceTag(tag);
}
};
const searchVisibleFunctions = useMemo(() => {
return availableFunctions.filter((entry) => { return availableFunctions.filter((entry) => {
if (referenceTag && referenceTag !== 'all') {
if (!(entry.tags || ['untagged']).includes(referenceTag)) {
return false;
}
}
if (!search) { if (!search) {
return true; return true;
} }
const lowerCaseSearch = search.toLowerCase(); const lowerCaseSearch = search.toLowerCase();
return ( return (
entry.name.toLowerCase().includes(lowerCaseSearch) || entry.name.toLowerCase().includes(lowerCaseSearch) ||
(entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false) (entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false)
); );
}); });
}, [search]); }, [search, referenceTag]);
const detailVisibleFunctions = useMemo(() => {
return searchVisibleFunctions.filter((x) => {
if (referenceTag === null || referenceTag === 'all') {
if (search) {
return true;
}
return x.name === selectedFunction;
} else {
return true;
}
});
}, [searchVisibleFunctions, selectedFunction, referenceTag]);
const onSearchTagFilterClick = () => {
setReferenceTag('all');
setSelectedFunction(null);
};
useEffect(() => {
if (selectedFunction) {
const el = document.getElementById(`doc-${selectedFunction}`);
const container = document.getElementById('reference-container');
container.scrollTo(0, el.offsetTop);
}
}, [selectedFunction]);
let setReferenceTag = (value) => settingsMap.setKey('referenceTag', value);
return ( return (
<div className="flex h-full w-full p-2 overflow-hidden"> <div className="flex h-full w-full overflow-hidden">
<div className="h-full flex flex-col gap-2 w-1/3 max-w-72 "> <div className="h-full text-foreground flex flex-col w-1/3 border-r border-muted">
{/* bg-background */}
<div className="w-full flex"> <div className="w-full flex">
<Textbox className="w-full" placeholder="Search" value={search} onChange={setSearch} /> <Textbox
className="w-full border-0 border-b border-muted"
placeholder="Search..."
value={search}
onChange={(e) => {
setSelectedFunction(null);
setReferenceTag('all');
setSearch(e);
}}
/>
</div> </div>
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
{visibleFunctions.map((entry, i) => ( {/* <div className="flex shrink-0 flex-wrap w-full overflow-auto border-y border-muted">
<a <ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}></ButtonGroup>
key={i} </div> */}
className="cursor-pointer text-foreground flex-none hover:bg-lineHighlight overflow-x-hidden px-1 text-ellipsis" {referenceTag && referenceTag !== 'all' && (
onClick={() => { <div className="w-72">
const el = document.getElementById(`doc-${i}`); <span
const container = document.getElementById('reference-container'); className="text-foreground border border-muted border-t-0 border-l-0 px-1 py-0.5 my-2 cursor-pointer font-sans"
container.scrollTo(0, el.offsetTop); onClick={onSearchTagFilterClick}
}}
> >
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */} {referenceTag}
</a> </span>
</div>
)}
<div className="h-full p-2 overflow-y-auto bg-opacity-50">
{searchVisibleFunctions.map((entry, i) => (
<Fragment key={`entry-${entry.name}`}>
<a
className={
'cursor-pointer hover:opacity-50 text-ellipsis block' +
(entry.name === selectedFunction ? 'bg-lineHighlight font-bold' : '')
}
onClick={() => {
if (entry.name === selectedFunction) {
setSelectedFunction(null);
} else {
setSelectedFunction(entry.name);
}
}}
>
{entry.name}
</a>{' '}
</Fragment>
))} ))}
</div> </div>
</div> </div>
<div <div
className="break-normal flex-grow flex-col overflow-y-auto overflow-x-hidden px-2 flex relative" className="w-2/3 break-normal flex-col overflow-y-auto overflow-x-hidden p-2 flex relative"
id="reference-container" id="reference-container"
> >
<div className="prose dark:prose-invert min-w-full px-1 "> <div className="prose dark:prose-invert min-w-full px-1 text-sm">
<h2>API Reference</h2> <h2>API Reference</h2>
<p> <p className="font-sans text-md">
This is the long list of functions you can use. Remember that you don't need to remember all of those and This is the long list of functions you can use. Remember that you don't need to remember all of those and
that you can already make music with a small set of functions! that you can already make music with a small set of functions!
</p> </p>
{visibleFunctions.map((entry, i) => ( <div>
<section key={i}> {/* <ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}/>*/}
<h3 id={`doc-${i}`}>{entry.name}</h3> {Object.entries(tagCounts)
.sort(([a], [b]) => a.localeCompare(b))
.map(([t, count]) => (
<span key={t}>
<a
className={[
'select-none text-white border border-muted px-1 py-0.5 my-2 cursor-pointer text-sm/8 no-underline font-sans',
`${referenceTag === t ? 'bg-muted text-foreground' : ''}`,
].join(' ')}
onClick={() => toggleTag(t)}
>
{t}&nbsp;({count})
</a>{' '}
</span>
))}
</div>
{detailVisibleFunctions.map((entry, i) => (
<section key={i} className="font-sans">
<div className="flex flex-row items-center mt-8 justify-between">
<h3 className="font-mono my-0 pt-4" id={`doc-${entry.name}`}>
{entry.name}
</h3>
{entry.tags && (
<span className="ml-2 text-xs text-foreground border border-muted px-1 py-0.5">
{entry.tags.join(', ')}
</span>
)}
</div>
{!!entry.synonyms_text && ( {!!entry.synonyms_text && (
<p> <p>
Synonyms: <code>{entry.synonyms_text}</code> Synonyms: <code>{entry.synonyms_text}</code>
</p> </p>
)} )}
{/* <small>{entry.meta.filename}</small> */}
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p> <p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
<ul> <ul>
{entry.params?.map(({ name, type, description }, i) => ( {entry.params?.map(({ name, type, description }, i) => (
@@ -102,14 +223,15 @@ export function Reference() {
))} ))}
</ul> </ul>
{entry.examples?.map((example, j) => ( {entry.examples?.map((example, j) => (
<pre className="bg-background" key={j}> <pre className="bg-background border border-muted" key={j}>
{example} {example}
</pre> </pre>
))} ))}
</section> </section>
))} )) || <p className="font-sans">Search or select a tag to get started.</p>}
{detailVisibleFunctions.length > 0 && <div className="h-screen" />}
</div> </div>
</div> </div>
</div> </div>
); );
} });
@@ -1,20 +0,0 @@
import React from 'react';
// value: ?ID, options: Map<ID, any>, onChange: ID => null, onClick: event => void, isDisabled: boolean
export function SelectInput({ value, options, onChange, onClick, isDisabled }) {
return (
<select
disabled={isDisabled}
onClick={onClick}
className="p-2 bg-background rounded-md text-foreground border-foreground"
value={value ?? ''}
onChange={(e) => onChange(e.target.value)}
>
{options.size == 0 && <option value={value}>{`${value ?? 'select an option'}`}</option>}
{Array.from(options.keys()).map((id) => (
<option key={id} className="bg-background" value={id}>
{options.get(id)}
</option>
))}
</select>
);
}
@@ -1,6 +1,5 @@
import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs'; import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs';
import { themes } from '@strudel/codemirror'; import { themes } from '@strudel/codemirror';
import { Textbox } from '../textbox/Textbox.jsx';
import { confirmAndReloadPage, isUdels } from '../../util.mjs'; import { confirmAndReloadPage, isUdels } from '../../util.mjs';
import { ButtonGroup } from './Forms.jsx'; import { ButtonGroup } from './Forms.jsx';
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
@@ -10,22 +9,60 @@ import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@
import { SpecialActionButton } from '../button/action-button.jsx'; import { SpecialActionButton } from '../button/action-button.jsx';
import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx'; import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx';
function cx(...classes) {
// : Array<string | undefined>
return classes.filter(Boolean).join(' ');
}
const inputClass =
'bg-background text-xs h-8 max-h-8 border border-box rounded-0 text-foreground border-muted placeholder-muted focus:outline-none focus:ring-0 focus:border-foreground';
export function Textbox({ onChange, className, ...inputProps }) {
return (
<input className={cx('px-2', inputClass, className)} onChange={(e) => onChange(e.target.value)} {...inputProps} />
);
}
function Checkbox({ label, value, onChange, disabled = false }) { function Checkbox({ label, value, onChange, disabled = false }) {
return ( return (
<label> <label className="text-xs">
<input disabled={disabled} type="checkbox" checked={value} onChange={onChange} /> <input
className={cx(
'bg-background text-sm border border-muted focus:outline-none focus:ring-0 focus:border-foreground',
)}
disabled={disabled}
type="checkbox"
checked={value}
onChange={onChange}
/>
{' ' + label} {' ' + label}
</label> </label>
); );
} }
function SelectInput({ value, options, onChange }) { // value: ?ID, options: Map<ID, any>, onChange: ID => null, onClick: event => void, isDisabled: boolean
export function SelectInputDuplicate({ value, options, onChange, onClick, isDisabled }) {
return ( return (
<select <select
className="p-2 bg-background rounded-md text-foreground border-foreground" disabled={isDisabled}
value={value} onClick={onClick}
className={cx('p-2', inputClass)}
value={value ?? ''}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
> >
{options.size == 0 && <option value={value}>{`${value ?? 'select an option'}`}</option>}
{Array.from(options.keys()).map((id) => (
<option key={id} className="bg-background" value={id}>
{options.get(id)}
</option>
))}
</select>
);
}
function SelectInput({ value, options, onChange }) {
return (
<select className={cx('p-2', inputClass)} value={value} onChange={(e) => onChange(e.target.value)}>
{Object.entries(options).map(([k, label]) => ( {Object.entries(options).map(([k, label]) => (
<option key={k} className="bg-background" value={k}> <option key={k} className="bg-background" value={k}>
{label} {label}
@@ -37,9 +74,9 @@ function SelectInput({ value, options, onChange }) {
function NumberSlider({ value, onChange, step = 1, ...rest }) { function NumberSlider({ value, onChange, step = 1, ...rest }) {
return ( return (
<div className="flex space-x-2 gap-1"> <div className="flex space-x-2 gap-1 overflow-hidden">
<input <input
className="p-2 grow" className="p-2 grow accent-foreground"
type="range" type="range"
value={value} value={value}
step={step} step={step}
@@ -50,7 +87,7 @@ function NumberSlider({ value, onChange, step = 1, ...rest }) {
type="number" type="number"
value={value} value={value}
step={step} step={step}
className="w-16 bg-background rounded-md" className={cx('w-16', inputClass)}
onChange={(e) => onChange(Number(e.target.value))} onChange={(e) => onChange(Number(e.target.value))}
/> />
</div> </div>
@@ -59,8 +96,8 @@ function NumberSlider({ value, onChange, step = 1, ...rest }) {
function FormItem({ label, children, sublabel }) { function FormItem({ label, children, sublabel }) {
return ( return (
<div className="grid gap-2"> <div className="grid gap-2 text-xs">
<label>{label}</label> <label className="text-sm">{label}</label>
{children} {children}
</div> </div>
); );
@@ -107,7 +144,6 @@ export function SettingsTab({ started }) {
panelPosition, panelPosition,
audioDeviceName, audioDeviceName,
audioEngineTarget, audioEngineTarget,
togglePanelTrigger,
maxPolyphony, maxPolyphony,
multiChannelOrbits, multiChannelOrbits,
isTabIndentationEnabled, isTabIndentationEnabled,
@@ -118,7 +154,7 @@ export function SettingsTab({ started }) {
const shouldAlwaysSync = isUdels(); const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
return ( return (
<div className="text-foreground p-4 space-y-4 w-full" style={{ fontFamily }}> <div className="p-4 text-foreground space-y-4 w-full" style={{ fontFamily }}>
{canChangeAudioDevice && ( {canChangeAudioDevice && (
<FormItem label="Audio Output Device"> <FormItem label="Audio Output Device">
<AudioDeviceSelector <AudioDeviceSelector
@@ -178,7 +214,7 @@ export function SettingsTab({ started }) {
<FormItem label="Theme"> <FormItem label="Theme">
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} /> <SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
</FormItem> </FormItem>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 font-sans"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormItem label="Font Family"> <FormItem label="Font Family">
<SelectInput <SelectInput
options={fontFamilyOptions} options={fontFamilyOptions}
@@ -219,13 +255,6 @@ export function SettingsTab({ started }) {
items={{ bottom: 'Bottom', right: 'Right' }} items={{ bottom: 'Bottom', right: 'Right' }}
></ButtonGroup> ></ButtonGroup>
</FormItem> </FormItem>
<FormItem label="Open Panel on: ">
<ButtonGroup
value={togglePanelTrigger}
onChange={(value) => settingsMap.setKey('togglePanelTrigger', value)}
items={{ click: 'Click', hover: 'Hover' }}
/>
</FormItem>
<FormItem label="More Settings"> <FormItem label="More Settings">
<Checkbox <Checkbox
label="Enable bracket matching" label="Enable bracket matching"
@@ -294,7 +323,7 @@ export function SettingsTab({ started }) {
value={isSyncEnabled} value={isSyncEnabled}
/> />
<Checkbox <Checkbox
label="Hide top buttons" label="Hide action buttons"
onChange={(cbEvent) => settingsMap.setKey('isButtonRowHidden', cbEvent.target.checked)} onChange={(cbEvent) => settingsMap.setKey('isButtonRowHidden', cbEvent.target.checked)}
value={isButtonRowHidden} value={isButtonRowHidden}
/> />
@@ -5,7 +5,7 @@ import { useMemo, useRef, useState } from 'react';
import { settingsMap, soundFilterType, useSettings } from '../../../settings.mjs'; import { settingsMap, soundFilterType, useSettings } from '../../../settings.mjs';
import { ButtonGroup } from './Forms.jsx'; import { ButtonGroup } from './Forms.jsx';
import ImportSoundsButton from './ImportSoundsButton.jsx'; import ImportSoundsButton from './ImportSoundsButton.jsx';
import { Textbox } from '../textbox/Textbox.jsx'; import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx';
import { ActionButton } from '../button/action-button.jsx'; import { ActionButton } from '../button/action-button.jsx';
import { confirmDialog } from '@src/repl/util.mjs'; import { confirmDialog } from '@src/repl/util.mjs';
import { clearIDB, userSamplesDBConfig } from '@src/repl/idbutils.mjs'; import { clearIDB, userSamplesDBConfig } from '@src/repl/idbutils.mjs';
@@ -77,11 +77,12 @@ export function SoundsTab() {
numRef.current = 0; numRef.current = 0;
}); });
return ( return (
<div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground"> <div id="sounds-tab" className="flex flex-col w-full h-full text-foreground">
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} /> <Textbox placeholder="Search..." className="border-0" value={search} onChange={(v) => setSearch(v)} />
<div className=" flex shrink-0 flex-wrap"> <div className="flex shrink-0 flex-wrap border-y border-muted">
<ButtonGroup <ButtonGroup
wrap
value={soundsFilter} value={soundsFilter}
onChange={(value) => settingsMap.setKey('soundsFilter', value)} onChange={(value) => settingsMap.setKey('soundsFilter', value)}
items={{ items={{
@@ -114,7 +115,7 @@ export function SoundsTab() {
/> />
)} )}
<div className="min-h-0 max-h-full grow overflow-auto text-sm break-normal bg-background p-2 rounded-md"> <div className="min-h-0 max-h-full grow overflow-auto break-normal p-2">
{soundEntries.map(([name, { data, onTrigger }]) => { {soundEntries.map(([name, { data, onTrigger }]) => {
return ( return (
<span <span
@@ -165,7 +166,7 @@ export function SoundsTab() {
); );
})} })}
{!soundEntries.length && soundsFilter === 'importSounds' ? ( {!soundEntries.length && soundsFilter === 'importSounds' ? (
<div className="prose dark:prose-invert min-w-full pt-2 pb-8 px-4"> <div className="prose dark:prose-invert min-w-full text-sm">
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} /> <ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
<p> <p>
To import sounds into strudel, they must be contained{' '} To import sounds into strudel, they must be contained{' '}
@@ -6,8 +6,8 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
export function WelcomeTab({ context }) { export function WelcomeTab({ context }) {
const { fontFamily } = useSettings(); const { fontFamily } = useSettings();
return ( return (
<div className="prose dark:prose-invert min-w-full pt-2 font-sans pb-8 px-4 " style={{ fontFamily }}> <div className="prose dark:prose-invert min-w-full py-4 font-sans px-4 text-sm" style={{ fontFamily }}>
<h3> welcome</h3> <h3>welcome</h3>
<p> <p>
You have found <span className="underline">strudel</span>, a new live coding platform to write dynamic music You have found <span className="underline">strudel</span>, a new live coding platform to write dynamic music
pieces in the browser! It is free and open-source and made for beginners and experts alike. To get started: pieces in the browser! It is free and open-source and made for beginners and experts alike. To get started:
@@ -30,7 +30,7 @@ export function WelcomeTab({ context }) {
</a>{' '} </a>{' '}
to ask any questions, give feedback or just say hello. to ask any questions, give feedback or just say hello.
</p> </p>
<h3> about</h3> <h3>about</h3>
<p> <p>
strudel is a JavaScript version of{' '} strudel is a JavaScript version of{' '}
<a href="https://tidalcycles.org/" target="_blank"> <a href="https://tidalcycles.org/" target="_blank">
@@ -1,14 +0,0 @@
import cx from '@src/cx.mjs';
export function Textbox({ onChange, className, ...inputProps }) {
return (
<input
className={cx(
'p-2 bg-background rounded-md border-foreground text-foreground placeholder-foreground',
className,
)}
onChange={(e) => onChange(e.target.value)}
{...inputProps}
/>
);
}

Some files were not shown because too many files have changed in this diff Show More