Merge branch 'main' into block-based-eval-squashed
This commit is contained in:
commit
269758ebdb
85 changed files with 1176 additions and 952 deletions
5
.astro/settings.json
Normal file
5
.astro/settings.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"devToolbar": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/codemirror/themes.mjs
vendored
1
packages/codemirror/themes.mjs
vendored
|
|
@ -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;`)
|
||||||
|
|
|
||||||
4
packages/codemirror/themes/CutiePi.mjs
vendored
4
packages/codemirror/themes/CutiePi.mjs
vendored
|
|
@ -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
packages/codemirror/themes/algoboy.mjs
vendored
1
packages/codemirror/themes/algoboy.mjs
vendored
|
|
@ -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
packages/codemirror/themes/androidstudio.mjs
vendored
2
packages/codemirror/themes/androidstudio.mjs
vendored
|
|
@ -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
packages/codemirror/themes/archBtw.mjs
vendored
1
packages/codemirror/themes/archBtw.mjs
vendored
|
|
@ -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
packages/codemirror/themes/atomone.mjs
vendored
2
packages/codemirror/themes/atomone.mjs
vendored
|
|
@ -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
packages/codemirror/themes/aura.mjs
vendored
2
packages/codemirror/themes/aura.mjs
vendored
|
|
@ -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
packages/codemirror/themes/bbedit.mjs
vendored
2
packages/codemirror/themes/bbedit.mjs
vendored
|
|
@ -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',
|
||||||
|
|
|
||||||
3
packages/codemirror/themes/blackscreen.mjs
vendored
3
packages/codemirror/themes/blackscreen.mjs
vendored
|
|
@ -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',
|
||||||
|
|
|
||||||
3
packages/codemirror/themes/bluescreen.mjs
vendored
3
packages/codemirror/themes/bluescreen.mjs
vendored
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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
packages/codemirror/themes/darcula.mjs
vendored
2
packages/codemirror/themes/darcula.mjs
vendored
|
|
@ -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
packages/codemirror/themes/dracula.mjs
vendored
2
packages/codemirror/themes/dracula.mjs
vendored
|
|
@ -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
packages/codemirror/themes/duotoneDark.mjs
vendored
2
packages/codemirror/themes/duotoneDark.mjs
vendored
|
|
@ -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
packages/codemirror/themes/eclipse.mjs
vendored
2
packages/codemirror/themes/eclipse.mjs
vendored
|
|
@ -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
packages/codemirror/themes/fruitDaw.mjs
vendored
1
packages/codemirror/themes/fruitDaw.mjs
vendored
|
|
@ -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
packages/codemirror/themes/githubDark.mjs
vendored
2
packages/codemirror/themes/githubDark.mjs
vendored
|
|
@ -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
packages/codemirror/themes/githubLight.mjs
vendored
2
packages/codemirror/themes/githubLight.mjs
vendored
|
|
@ -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
packages/codemirror/themes/green-text.mjs
vendored
1
packages/codemirror/themes/green-text.mjs
vendored
|
|
@ -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
packages/codemirror/themes/gruvboxDark.mjs
vendored
2
packages/codemirror/themes/gruvboxDark.mjs
vendored
|
|
@ -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
packages/codemirror/themes/gruvboxLight.mjs
vendored
2
packages/codemirror/themes/gruvboxLight.mjs
vendored
|
|
@ -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
packages/codemirror/themes/materialDark.mjs
vendored
2
packages/codemirror/themes/materialDark.mjs
vendored
|
|
@ -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',
|
||||||
|
|
|
||||||
4
packages/codemirror/themes/materialLight.mjs
vendored
4
packages/codemirror/themes/materialLight.mjs
vendored
|
|
@ -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
packages/codemirror/themes/monokai.mjs
vendored
2
packages/codemirror/themes/monokai.mjs
vendored
|
|
@ -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
packages/codemirror/themes/noctisLilac.mjs
vendored
2
packages/codemirror/themes/noctisLilac.mjs
vendored
|
|
@ -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
packages/codemirror/themes/nord.mjs
vendored
2
packages/codemirror/themes/nord.mjs
vendored
|
|
@ -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
packages/codemirror/themes/red-text.mjs
vendored
1
packages/codemirror/themes/red-text.mjs
vendored
|
|
@ -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
packages/codemirror/themes/solarizedDark.mjs
vendored
2
packages/codemirror/themes/solarizedDark.mjs
vendored
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
packages/codemirror/themes/sonic-pink.mjs
vendored
1
packages/codemirror/themes/sonic-pink.mjs
vendored
|
|
@ -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
packages/codemirror/themes/strudel-theme.mjs
vendored
1
packages/codemirror/themes/strudel-theme.mjs
vendored
|
|
@ -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
packages/codemirror/themes/sublime.mjs
vendored
2
packages/codemirror/themes/sublime.mjs
vendored
|
|
@ -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',
|
||||||
|
|
|
||||||
3
packages/codemirror/themes/teletext.mjs
vendored
3
packages/codemirror/themes/teletext.mjs
vendored
|
|
@ -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,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',
|
||||||
|
|
|
||||||
6
packages/codemirror/themes/tokyoNight.mjs
vendored
6
packages/codemirror/themes/tokyoNight.mjs
vendored
|
|
@ -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
packages/codemirror/themes/tokyoNightDay.mjs
vendored
2
packages/codemirror/themes/tokyoNightDay.mjs
vendored
|
|
@ -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
packages/codemirror/themes/vscodeDark.mjs
vendored
2
packages/codemirror/themes/vscodeDark.mjs
vendored
|
|
@ -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
packages/codemirror/themes/vscodeLight.mjs
vendored
2
packages/codemirror/themes/vscodeLight.mjs
vendored
|
|
@ -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',
|
||||||
|
|
|
||||||
3
packages/codemirror/themes/whitescreen.mjs
vendored
3
packages/codemirror/themes/whitescreen.mjs
vendored
|
|
@ -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
packages/codemirror/themes/xcodeLight.mjs
vendored
2
packages/codemirror/themes/xcodeLight.mjs
vendored
|
|
@ -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
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -818,7 +818,6 @@ export class Pattern {
|
||||||
* @name layer
|
* @name layer
|
||||||
* @tags combiners
|
* @tags combiners
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @synonyms apply
|
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8"
|
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8"
|
||||||
|
|
@ -1055,91 +1054,88 @@ function _composeOp(a, b, func) {
|
||||||
return func(a, b);
|
return func(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make composers
|
// pattern composers
|
||||||
(function () {
|
const COMPOSERS = {
|
||||||
// pattern composers
|
set: [(a, b) => b],
|
||||||
const composers = {
|
keep: [(a) => a],
|
||||||
set: [(a, b) => b],
|
keepif: [(a, b) => (b ? a : undefined)],
|
||||||
keep: [(a) => a],
|
|
||||||
keepif: [(a, b) => (b ? a : undefined)],
|
|
||||||
|
|
||||||
// numerical functions
|
// numerical functions
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Assumes a pattern of numbers. Adds the given number to each item in the pattern.
|
* Assumes a pattern of numbers. Adds the given number to each item in the pattern.
|
||||||
* @name add
|
* @name add
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @tags math
|
* @tags math
|
||||||
* @example
|
* @example
|
||||||
* // Here, the triad 0, 2, 4 is shifted by different amounts
|
* // Here, the triad 0, 2, 4 is shifted by different amounts
|
||||||
* n("0 2 4".add("<0 3 4 0>")).scale("C:major")
|
* n("0 2 4".add("<0 3 4 0>")).scale("C:major")
|
||||||
* // Without add, the equivalent would be:
|
* // Without add, the equivalent would be:
|
||||||
* // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major")
|
* // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major")
|
||||||
* @example
|
* @example
|
||||||
* // You can also use add with notes:
|
* // You can also use add with notes:
|
||||||
* note("c3 e3 g3".add("<0 5 7 0>"))
|
* note("c3 e3 g3".add("<0 5 7 0>"))
|
||||||
* // Behind the scenes, the notes are converted to midi numbers:
|
* // Behind the scenes, the notes are converted to midi numbers:
|
||||||
* // note("48 52 55".add("<0 5 7 0>"))
|
* // note("48 52 55".add("<0 5 7 0>"))
|
||||||
*/
|
*/
|
||||||
add: [numeralArgs((a, b) => a + b)], // support string concatenation
|
add: [numeralArgs((a, b) => a + b)], // support string concatenation
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Like add, but the given numbers are subtracted.
|
* Like add, but the given numbers are subtracted.
|
||||||
* @name sub
|
* @name sub
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @tags math
|
* @tags math
|
||||||
* @example
|
* @example
|
||||||
* n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor")
|
* n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor")
|
||||||
* // See add for more information.
|
* // See add for more information.
|
||||||
*/
|
*/
|
||||||
sub: [numeralArgs((a, b) => a - b)],
|
sub: [numeralArgs((a, b) => a - b)],
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Multiplies each number by the given factor.
|
* Multiplies each number by the given factor.
|
||||||
* @name mul
|
* @name mul
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @tags math
|
* @tags math
|
||||||
* @example
|
* @example
|
||||||
* "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq()
|
* "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq()
|
||||||
*/
|
*/
|
||||||
mul: [numeralArgs((a, b) => a * b)],
|
mul: [numeralArgs((a, b) => a * b)],
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Divides each number by the given factor.
|
* Divides each number by the given factor.
|
||||||
* @name div
|
* @name div
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @tags math
|
* @tags math
|
||||||
*/
|
*/
|
||||||
div: [numeralArgs((a, b) => a / b)],
|
div: [numeralArgs((a, b) => a / b)],
|
||||||
mod: [numeralArgs(_mod)],
|
mod: [numeralArgs(_mod)],
|
||||||
pow: [numeralArgs(Math.pow)],
|
pow: [numeralArgs(Math.pow)],
|
||||||
log2: [numeralArgs(Math.log2)],
|
log2: [numeralArgs(Math.log2)],
|
||||||
band: [numeralArgs((a, b) => a & b)],
|
band: [numeralArgs((a, b) => a & b)],
|
||||||
bor: [numeralArgs((a, b) => a | b)],
|
bor: [numeralArgs((a, b) => a | b)],
|
||||||
bxor: [numeralArgs((a, b) => a ^ b)],
|
bxor: [numeralArgs((a, b) => a ^ b)],
|
||||||
blshift: [numeralArgs((a, b) => a << b)],
|
blshift: [numeralArgs((a, b) => a << b)],
|
||||||
brshift: [numeralArgs((a, b) => a >> b)],
|
brshift: [numeralArgs((a, b) => a >> b)],
|
||||||
|
|
||||||
// TODO - force numerical comparison if both look like numbers?
|
// TODO - force numerical comparison if both look like numbers?
|
||||||
lt: [(a, b) => a < b],
|
lt: [(a, b) => a < b],
|
||||||
gt: [(a, b) => a > b],
|
gt: [(a, b) => a > b],
|
||||||
lte: [(a, b) => a <= b],
|
lte: [(a, b) => a <= b],
|
||||||
gte: [(a, b) => a >= b],
|
gte: [(a, b) => a >= b],
|
||||||
eq: [(a, b) => a == b],
|
eq: [(a, b) => a == b],
|
||||||
eqt: [(a, b) => a === b],
|
eqt: [(a, b) => a === b],
|
||||||
ne: [(a, b) => a != b],
|
ne: [(a, b) => a != b],
|
||||||
net: [(a, b) => a !== b],
|
net: [(a, b) => a !== b],
|
||||||
and: [(a, b) => a && b],
|
and: [(a, b) => a && b],
|
||||||
or: [(a, b) => a || b],
|
or: [(a, b) => a || b],
|
||||||
|
|
||||||
// bitwise ops
|
// bitwise ops
|
||||||
func: [(a, b) => b(a)],
|
func: [(a, b) => b(a)],
|
||||||
};
|
};
|
||||||
|
|
||||||
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart', 'Poly'];
|
|
||||||
|
|
||||||
|
const _setupAlignments = () => {
|
||||||
// generate methods to do what and how
|
// generate methods to do what and how
|
||||||
for (const [what, [op, preprocess]] of Object.entries(composers)) {
|
for (const [what, [op, preprocess]] of Object.entries(COMPOSERS)) {
|
||||||
// make plain version, e.g. pat._add(value) adds that plain value
|
// make plain version, e.g. pat._add(value) adds that plain value
|
||||||
// to all the values in pat
|
// to all the values in pat
|
||||||
Pattern.prototype['_' + what] = function (value) {
|
Pattern.prototype['_' + what] = function (value) {
|
||||||
|
|
@ -1148,16 +1144,18 @@ function _composeOp(a, b, func) {
|
||||||
|
|
||||||
// make patternified monster version
|
// make patternified monster version
|
||||||
Object.defineProperty(Pattern.prototype, what, {
|
Object.defineProperty(Pattern.prototype, what, {
|
||||||
|
// Set to configurable so we can update if the default alignment changes
|
||||||
|
configurable: true,
|
||||||
// a getter that returns a function, so 'pat' can be
|
// a getter that returns a function, so 'pat' can be
|
||||||
// accessed by closures that are methods of that function..
|
// accessed by closures that are methods of that function..
|
||||||
get: function () {
|
get: function () {
|
||||||
const pat = this;
|
const pat = this;
|
||||||
|
|
||||||
// wrap the 'in' function as default behaviour
|
// wrap the 'in' function as default behaviour
|
||||||
const wrapper = (...other) => pat[what]['in'](...other);
|
const wrapper = (...other) => pat[what][DEFAULT_ALIGNMENT](...other);
|
||||||
|
|
||||||
// add methods to that function for each behaviour
|
// add methods to that function for each behaviour
|
||||||
for (const how of hows) {
|
for (const how of ALIGNMENTS) {
|
||||||
wrapper[how.toLowerCase()] = function (...other) {
|
wrapper[how.toLowerCase()] = function (...other) {
|
||||||
var howpat = pat;
|
var howpat = pat;
|
||||||
other = sequence(other);
|
other = sequence(other);
|
||||||
|
|
@ -1182,15 +1180,23 @@ function _composeOp(a, b, func) {
|
||||||
return wrapper;
|
return wrapper;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
|
||||||
for (const how of hows) {
|
|
||||||
Pattern.prototype[how.toLowerCase()] = function (...args) {
|
|
||||||
return this.set[how.toLowerCase()](args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let DEFAULT_ALIGNMENT = 'in';
|
||||||
|
const ALIGNMENTS = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart', 'Poly'];
|
||||||
|
const ALIGNMENT_KEYS = ALIGNMENTS.map((how) => how.toLowerCase());
|
||||||
|
|
||||||
|
// Make composers
|
||||||
|
(function () {
|
||||||
|
_setupAlignments();
|
||||||
|
|
||||||
|
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
||||||
|
for (const how of ALIGNMENTS) {
|
||||||
|
Pattern.prototype[how.toLowerCase()] = function (...args) {
|
||||||
|
return this.set[how.toLowerCase()](args);
|
||||||
|
};
|
||||||
|
}
|
||||||
// binary composers
|
// binary composers
|
||||||
/**
|
/**
|
||||||
* Applies the given structure to the pattern:
|
* Applies the given structure to the pattern:
|
||||||
|
|
@ -1249,6 +1255,28 @@ function _composeOp(a, b, func) {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default method of combining events from two patterns (aka [alignment](https://strudel.cc/technical-manual/alignment/)) in Strudel.
|
||||||
|
* The default method is 'in', meaning that patterns to the left will (typically) dictate the event timings when combined with patterns to the right.
|
||||||
|
* By changing alignment to 'out', the opposite will happen. With 'mix', they will combine their event timings.
|
||||||
|
*
|
||||||
|
* Note that we say the _default_ method, because alignments can also be set explicitly with calls like
|
||||||
|
* 'add.mix', 'set.squeeze', etc.
|
||||||
|
*
|
||||||
|
* @param {string} method Default join method to use. Options: 'in', 'out', 'mix', 'squeeze', 'squeezeout', 'reset', 'restart', 'poly'
|
||||||
|
* @tags combiners
|
||||||
|
* @example
|
||||||
|
* setDefaultJoin('mix') // also try 'in', 'out', 'squeeze', etc.
|
||||||
|
* s("saw").vel("1 0.5").note("F A C E").delay("0 0.2 0.3")
|
||||||
|
*/
|
||||||
|
export const setDefaultJoin = (alignment) => {
|
||||||
|
alignment = alignment?.toLowerCase();
|
||||||
|
if (DEFAULT_ALIGNMENT !== alignment && ALIGNMENT_KEYS.includes(alignment)) {
|
||||||
|
DEFAULT_ALIGNMENT = alignment;
|
||||||
|
_setupAlignments();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// aliases
|
// aliases
|
||||||
export const polyrhythm = stack;
|
export const polyrhythm = stack;
|
||||||
export const pr = stack;
|
export const pr = stack;
|
||||||
|
|
@ -1536,10 +1564,10 @@ export function arrange(...sections) {
|
||||||
* @tags combiners
|
* @tags combiners
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
seqPLoop([0, 2, "bd(3,8)"],
|
seqPLoop(
|
||||||
[1, 3, "cp(3,8)"]
|
[0, 2, "bd(3,8)"],
|
||||||
)
|
[1, 3, "cp(3,8)"]
|
||||||
.sound()
|
).sound()
|
||||||
*/
|
*/
|
||||||
export function seqPLoop(...parts) {
|
export function seqPLoop(...parts) {
|
||||||
let total = Fraction(0);
|
let total = Fraction(0);
|
||||||
|
|
@ -1582,7 +1610,7 @@ export function sequence(...pats) {
|
||||||
|
|
||||||
/** Like **cat**, but the items are crammed into one cycle.
|
/** Like **cat**, but the items are crammed into one cycle.
|
||||||
* @tags combiners
|
* @tags combiners
|
||||||
* @synonyms seq, fastcat
|
* @synonyms fastcat
|
||||||
* @example
|
* @example
|
||||||
* seq("e5", "b4", ["d5", "c5"]).note()
|
* seq("e5", "b4", ["d5", "c5"]).note()
|
||||||
* // "e5 b4 [d5 c5]".note()
|
* // "e5 b4 [d5 c5]".note()
|
||||||
|
|
@ -2109,13 +2137,12 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like layer, but with a single function:
|
* Applies the given function to the pattern. Like layer, but with a single function:
|
||||||
* @tags temporal
|
* @tags combiners
|
||||||
* @name apply
|
* @name apply
|
||||||
* @example
|
* @example
|
||||||
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
|
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
|
||||||
*/
|
*/
|
||||||
// TODO: remove or dedupe with layer?
|
|
||||||
export const apply = register('apply', function (func, pat) {
|
export const apply = register('apply', function (func, pat) {
|
||||||
return func(pat);
|
return func(pat);
|
||||||
});
|
});
|
||||||
|
|
@ -2472,7 +2499,7 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel.
|
* The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel.
|
||||||
* @tags temporal
|
* @tags temporal, superdough
|
||||||
* @example
|
* @example
|
||||||
* s("bd lt [~ ht] mt cp ~ bd hh").jux(rev)
|
* s("bd lt [~ ht] mt cp ~ bd hh").jux(rev)
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -2486,7 +2513,7 @@ export const jux = register('jux', function (func, pat) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Superimpose and offset multiple times, applying the given function each time.
|
* Superimpose and offset multiple times, applying the given function each time.
|
||||||
* @tags temporal
|
* @tags temporal, functional
|
||||||
* @name echoWith
|
* @name echoWith
|
||||||
* @synonyms echowith, stutWith, stutwith
|
* @synonyms echowith, stutWith, stutwith
|
||||||
* @param {number} times how many times to repeat
|
* @param {number} times how many times to repeat
|
||||||
|
|
@ -2660,7 +2687,7 @@ export const { repeatCycles } = register(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle).
|
* Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle).
|
||||||
* @tags temporal
|
* @tags temporal, functional
|
||||||
* @name chunk
|
* @name chunk
|
||||||
* @synonyms slowChunk, slowchunk
|
* @synonyms slowChunk, slowchunk
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
|
|
@ -2824,7 +2851,7 @@ Pattern.prototype.tag = function (tag) {
|
||||||
/**
|
/**
|
||||||
* Filters haps using the given function
|
* Filters haps using the given function
|
||||||
* @name filter
|
* @name filter
|
||||||
* @tags temporal
|
* @tags temporal, functional
|
||||||
* @param {Function} test function to test Hap
|
* @param {Function} test function to test Hap
|
||||||
* @example
|
* @example
|
||||||
* s("hh!7 oh").filter(hap => hap.value.s === 'hh')
|
* s("hh!7 oh").filter(hap => hap.value.s === 'hh')
|
||||||
|
|
@ -2834,7 +2861,7 @@ export const filter = register('filter', (test, pat) => pat.withHaps((haps) => h
|
||||||
/**
|
/**
|
||||||
* Filters haps by their begin time
|
* Filters haps by their begin time
|
||||||
* @name filterWhen
|
* @name filterWhen
|
||||||
* @tags temporal
|
* @tags temporal, functional
|
||||||
* @param {Function} test function to test Hap.whole.begin
|
* @param {Function} test function to test Hap.whole.begin
|
||||||
* @example
|
* @example
|
||||||
* oneCycle: s("bd*4").filterWhen((t) => t < 1)
|
* oneCycle: s("bd*4").filterWhen((t) => t < 1)
|
||||||
|
|
@ -2844,7 +2871,7 @@ export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) =
|
||||||
/**
|
/**
|
||||||
* Use within to apply a function to only a part of a pattern.
|
* Use within to apply a function to only a part of a pattern.
|
||||||
* @name within
|
* @name within
|
||||||
* @tags temporal
|
* @tags temporal, functional
|
||||||
* @param {number} start start within cycle (0 - 1)
|
* @param {number} start start within cycle (0 - 1)
|
||||||
* @param {number} end end within cycle (0 - 1). Must be > start
|
* @param {number} end end within cycle (0 - 1). Must be > start
|
||||||
* @param {Function} func function to be applied to the sub-pattern
|
* @param {Function} func function to be applied to the sub-pattern
|
||||||
|
|
@ -2919,7 +2946,7 @@ export function _match(span, hap_p) {
|
||||||
* *Experimental*
|
* *Experimental*
|
||||||
*
|
*
|
||||||
* Speeds a pattern up or down, to fit to the given number of steps per cycle.
|
* Speeds a pattern up or down, to fit to the given number of steps per cycle.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @example
|
* @example
|
||||||
* sound("bd sd cp").pace(4)
|
* sound("bd sd cp").pace(4)
|
||||||
* // The same as sound("{bd sd cp}%4") or sound("<bd sd cp>*4")
|
* // The same as sound("{bd sd cp}%4") or sound("<bd sd cp>*4")
|
||||||
|
|
@ -2961,7 +2988,7 @@ export function _polymeterListSteps(steps, ...args) {
|
||||||
* *Experimental*
|
* *Experimental*
|
||||||
*
|
*
|
||||||
* Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps.
|
* Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @synonyms pm
|
* @synonyms pm
|
||||||
* @example
|
* @example
|
||||||
* // The same as note("{c eb g, c2 g2}%6")
|
* // The same as note("{c eb g, c2 g2}%6")
|
||||||
|
|
@ -2994,7 +3021,7 @@ export function polymeter(...args) {
|
||||||
* The steps can either be inferred from the pattern, or provided as a [length, pattern] pair.
|
* The steps can either be inferred from the pattern, or provided as a [length, pattern] pair.
|
||||||
* Has the alias `timecat`.
|
* Has the alias `timecat`.
|
||||||
* @name stepcat
|
* @name stepcat
|
||||||
* @tags combiners
|
* @tags stepwise
|
||||||
* @synonyms timeCat, timecat
|
* @synonyms timeCat, timecat
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -3052,7 +3079,7 @@ export function stepcat(...timepats) {
|
||||||
* Concatenates patterns stepwise, according to an inferred 'steps per cycle'.
|
* Concatenates patterns stepwise, according to an inferred 'steps per cycle'.
|
||||||
* Similar to `stepcat`, but if an argument is a list, the whole pattern will alternate between the elements in the list.
|
* Similar to `stepcat`, but if an argument is a list, the whole pattern will alternate between the elements in the list.
|
||||||
*
|
*
|
||||||
* @tags combiners
|
* @tags stepwise
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* stepalt(["bd cp", "mt"], "bd").sound()
|
* stepalt(["bd cp", "mt"], "bd").sound()
|
||||||
|
|
@ -3079,7 +3106,7 @@ export function stepalt(...groups) {
|
||||||
*
|
*
|
||||||
* Takes the given number of steps from a pattern (dropping the rest).
|
* Takes the given number of steps from a pattern (dropping the rest).
|
||||||
* A positive number will take steps from the start of a pattern, and a negative number from the end.
|
* A positive number will take steps from the start of a pattern, and a negative number from the end.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* "bd cp ht mt".take("2").sound()
|
* "bd cp ht mt".take("2").sound()
|
||||||
|
|
@ -3124,7 +3151,7 @@ export const take = stepRegister('take', function (i, pat) {
|
||||||
*
|
*
|
||||||
* Drops the given number of steps from a pattern.
|
* Drops the given number of steps from a pattern.
|
||||||
* A positive number will drop steps from the start of a pattern, and a negative number from the end.
|
* A positive number will drop steps from the start of a pattern, and a negative number from the end.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* "tha dhi thom nam".drop("1").sound().bank("mridangam")
|
* "tha dhi thom nam".drop("1").sound().bank("mridangam")
|
||||||
|
|
@ -3153,7 +3180,7 @@ export const drop = stepRegister('drop', function (i, pat) {
|
||||||
* `extend` is similar to `fast` in that it increases its density, but it also increases the step count
|
* `extend` is similar to `fast` in that it increases its density, but it also increases the step count
|
||||||
* accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas
|
* accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas
|
||||||
* `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`.
|
* `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @example
|
* @example
|
||||||
* stepcat(
|
* stepcat(
|
||||||
* sound("bd bd - cp").extend(2),
|
* sound("bd bd - cp").extend(2),
|
||||||
|
|
@ -3172,7 +3199,7 @@ export const extend = stepRegister('extend', function (factor, pat) {
|
||||||
* `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`.
|
* `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`.
|
||||||
*
|
*
|
||||||
* TODO: find out how this function differs from extend
|
* TODO: find out how this function differs from extend
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @example
|
* @example
|
||||||
* stepcat(
|
* stepcat(
|
||||||
* sound("bd bd - cp").replicate(2),
|
* sound("bd bd - cp").replicate(2),
|
||||||
|
|
@ -3187,7 +3214,7 @@ export const replicate = stepRegister('replicate', function (factor, pat) {
|
||||||
* *Experimental*
|
* *Experimental*
|
||||||
*
|
*
|
||||||
* Expands the step size of the pattern by the given factor.
|
* Expands the step size of the pattern by the given factor.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @example
|
* @example
|
||||||
* sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8)
|
* sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8)
|
||||||
*/
|
*/
|
||||||
|
|
@ -3199,7 +3226,7 @@ export const expand = stepRegister('expand', function (factor, pat) {
|
||||||
* *Experimental*
|
* *Experimental*
|
||||||
*
|
*
|
||||||
* Contracts the step size of the pattern by the given factor. See also `expand`.
|
* Contracts the step size of the pattern by the given factor. See also `expand`.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @example
|
* @example
|
||||||
* sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8)
|
* sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8)
|
||||||
*/
|
*/
|
||||||
|
|
@ -3254,7 +3281,7 @@ export const shrinklist = (amount, pat) => pat.shrinklist(amount);
|
||||||
* Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with `:`),
|
* Progressively shrinks the pattern by 'n' steps until there's nothing left, or if a second value is given (using mininotation list syntax with `:`),
|
||||||
* that number of times.
|
* that number of times.
|
||||||
* A positive number will progressively drop steps from the start of a pattern, and a negative number from the end.
|
* A positive number will progressively drop steps from the start of a pattern, and a negative number from the end.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* "tha dhi thom nam".shrink("1").sound()
|
* "tha dhi thom nam".shrink("1").sound()
|
||||||
|
|
@ -3294,7 +3321,7 @@ export const shrink = register(
|
||||||
* Progressively grows the pattern by 'n' steps until the full pattern is played, or if a second value is given (using mininotation list syntax with `:`),
|
* Progressively grows the pattern by 'n' steps until the full pattern is played, or if a second value is given (using mininotation list syntax with `:`),
|
||||||
* that number of times.
|
* that number of times.
|
||||||
* A positive number will progressively grow steps from the start of a pattern, and a negative number from the end.
|
* A positive number will progressively grow steps from the start of a pattern, and a negative number from the end.
|
||||||
* @tags temporal
|
* @tags stepwise
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* "tha dhi thom nam".grow("1").sound()
|
* "tha dhi thom nam".grow("1").sound()
|
||||||
|
|
@ -3335,7 +3362,7 @@ export const grow = register(
|
||||||
* on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using `pace` to set the
|
* on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using `pace` to set the
|
||||||
* number of steps per cycle is therefore usually recommended.
|
* number of steps per cycle is therefore usually recommended.
|
||||||
*
|
*
|
||||||
* @tags combiners
|
* @tags stepwise
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* "[c g]".tour("e f", "e f g", "g f e c").note()
|
* "[c g]".tour("e f", "e f g", "g f e c").note()
|
||||||
|
|
@ -3362,7 +3389,7 @@ Pattern.prototype.tour = function (...many) {
|
||||||
* 'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle.
|
* 'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle.
|
||||||
* Using `pace` to set the number of steps per cycle is therefore usually recommended.
|
* Using `pace` to set the number of steps per cycle is therefore usually recommended.
|
||||||
*
|
*
|
||||||
* @tags combiners
|
* @tags stepwise
|
||||||
* @returns {Pattern}
|
* @returns {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* zip("e f", "e f g", "g [f e] a f4 c").note()
|
* zip("e f", "e f g", "g [f e] a f4 c").note()
|
||||||
|
|
@ -3414,7 +3441,7 @@ Pattern.prototype.steps = Pattern.prototype.pace;
|
||||||
* Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'.
|
* Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'.
|
||||||
* It turns a pattern of samples into a pattern of parts of samples.
|
* It turns a pattern of samples into a pattern of parts of samples.
|
||||||
* @name chop
|
* @name chop
|
||||||
* @tags temporal
|
* @tags samples
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -3445,7 +3472,7 @@ export const chop = register('chop', function (n, pat) {
|
||||||
/**
|
/**
|
||||||
* Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop.
|
* Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop.
|
||||||
* @name striate
|
* @name striate
|
||||||
* @tags temporal
|
* @tags samples
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -3464,14 +3491,13 @@ export const striate = register('striate', function (n, pat) {
|
||||||
/**
|
/**
|
||||||
* Makes the sample fit the given number of cycles by changing the speed.
|
* Makes the sample fit the given number of cycles by changing the speed.
|
||||||
* @name loopAt
|
* @name loopAt
|
||||||
* @tags temporal
|
* @tags samples, pitch
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
||||||
* s("rhodes").loopAt(2)
|
* s("rhodes").loopAt(2)
|
||||||
*/
|
*/
|
||||||
// TODO - global cps clock
|
|
||||||
const _loopAt = function (factor, pat, cps = 0.5) {
|
const _loopAt = function (factor, pat, cps = 0.5) {
|
||||||
return pat
|
return pat
|
||||||
.speed((1 / factor) * cps)
|
.speed((1 / factor) * cps)
|
||||||
|
|
@ -3479,11 +3505,16 @@ const _loopAt = function (factor, pat, cps = 0.5) {
|
||||||
.slow(factor);
|
.slow(factor);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
|
||||||
|
const steps = pat._steps ? pat._steps.div(factor) : undefined;
|
||||||
|
return new Pattern((state) => _loopAt(factor, pat, state.controls._cps).query(state), steps);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
|
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
|
||||||
* Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points.
|
* Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points.
|
||||||
* @name slice
|
* @name slice
|
||||||
* @tags temporal
|
* @tags samples
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -3537,7 +3568,7 @@ Pattern.prototype.onTriggerTime = function (func) {
|
||||||
/**
|
/**
|
||||||
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
|
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
|
||||||
* @name splice
|
* @name splice
|
||||||
* @tags temporal
|
* @tags samples, pitch
|
||||||
* @example
|
* @example
|
||||||
* samples('github:tidalcycles/dirt-samples')
|
* samples('github:tidalcycles/dirt-samples')
|
||||||
* s("breaks165")
|
* s("breaks165")
|
||||||
|
|
@ -3566,16 +3597,11 @@ export const splice = register(
|
||||||
false, // turns off auto-patternification
|
false, // turns off auto-patternification
|
||||||
);
|
);
|
||||||
|
|
||||||
export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
|
|
||||||
const steps = pat._steps ? pat._steps.div(factor) : undefined;
|
|
||||||
return new Pattern((state) => _loopAt(factor, pat, state.controls._cps).query(state), steps);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the sample fit its event duration. Good for rhythmical loops like drum breaks.
|
* Makes the sample fit its event duration. Good for rhythmical loops like drum breaks.
|
||||||
* Similar to `loopAt`.
|
* Similar to `loopAt`.
|
||||||
* @name fit
|
* @name fit
|
||||||
* @tags temporal
|
* @tags samples, pitch
|
||||||
* @example
|
* @example
|
||||||
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
||||||
* s("rhodes/2").fit()
|
* s("rhodes/2").fit()
|
||||||
|
|
@ -3597,18 +3623,16 @@ export const fit = register('fit', (pat) =>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the sample fit the given number of cycles and cps value, by
|
* Makes the sample fit the given number of cycles and cps value, by
|
||||||
* changing the speed. Please note that at some point cps will be
|
* changing the speed. deprecated: use loopAt or fit instead, together with setCps / setCpm.
|
||||||
* given by a global clock and this function will be
|
|
||||||
* deprecated/removed.
|
|
||||||
* @name loopAtCps
|
* @name loopAtCps
|
||||||
* @tags temporal
|
* @tags samples, pitch
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
|
* @deprecated
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
||||||
* s("rhodes").loopAtCps(4,1.5).cps(1.5)
|
* s("rhodes").loopAtCps(4,1.5).cps(1.5)
|
||||||
*/
|
*/
|
||||||
// TODO - global cps clock
|
|
||||||
export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
|
export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
|
||||||
return _loopAt(factor, pat, cps);
|
return _loopAt(factor, pat, cps);
|
||||||
});
|
});
|
||||||
|
|
@ -3630,7 +3654,7 @@ let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5);
|
||||||
* - 1 = (no left, full right)
|
* - 1 = (no left, full right)
|
||||||
*
|
*
|
||||||
* @name xfade
|
* @name xfade
|
||||||
* @tags combiners
|
* @tags amplitude
|
||||||
* @example
|
* @example
|
||||||
* xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8"))
|
* xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8"))
|
||||||
*/
|
*/
|
||||||
|
|
@ -3756,7 +3780,7 @@ const _distortWithAlg = function (name) {
|
||||||
* Soft-clipping distortion
|
* Soft-clipping distortion
|
||||||
*
|
*
|
||||||
* @name soft
|
* @name soft
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3767,7 +3791,7 @@ export const soft = _distortWithAlg('soft');
|
||||||
* Hard-clipping distortion
|
* Hard-clipping distortion
|
||||||
*
|
*
|
||||||
* @name hard
|
* @name hard
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3778,7 +3802,7 @@ export const hard = _distortWithAlg('hard');
|
||||||
* Cubic polynomial distortion
|
* Cubic polynomial distortion
|
||||||
*
|
*
|
||||||
* @name cubic
|
* @name cubic
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3789,7 +3813,7 @@ export const cubic = _distortWithAlg('cubic');
|
||||||
* Diode-emulating distortion
|
* Diode-emulating distortion
|
||||||
*
|
*
|
||||||
* @name diode
|
* @name diode
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3800,7 +3824,7 @@ export const diode = _distortWithAlg('diode');
|
||||||
* Asymmetrical diode distortion
|
* Asymmetrical diode distortion
|
||||||
*
|
*
|
||||||
* @name asym
|
* @name asym
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3811,7 +3835,7 @@ export const asym = _distortWithAlg('asym');
|
||||||
* Wavefolding distortion
|
* Wavefolding distortion
|
||||||
*
|
*
|
||||||
* @name fold
|
* @name fold
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3822,7 +3846,7 @@ export const fold = _distortWithAlg('fold');
|
||||||
* Wavefolding distortion composed with sinusoid
|
* Wavefolding distortion composed with sinusoid
|
||||||
*
|
*
|
||||||
* @name sinefold
|
* @name sinefold
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3833,7 +3857,7 @@ export const sinefold = _distortWithAlg('sinefold');
|
||||||
* Distortion via Chebyshev polynomials
|
* Distortion via Chebyshev polynomials
|
||||||
*
|
*
|
||||||
* @name chebyshev
|
* @name chebyshev
|
||||||
* @tags fx
|
* @tags distortion, superdough
|
||||||
* @param {number | Pattern} distortion amount of distortion to apply
|
* @param {number | Pattern} distortion amount of distortion to apply
|
||||||
* @param {number | Pattern} volume linear postgain of the distortion
|
* @param {number | Pattern} volume linear postgain of the distortion
|
||||||
*
|
*
|
||||||
|
|
@ -3867,7 +3891,7 @@ const _ensureListPattern = (list) => {
|
||||||
* Can also be used to create a new synth via `s('user').partials(...)`
|
* Can also be used to create a new synth via `s('user').partials(...)`
|
||||||
*
|
*
|
||||||
* @name partials
|
* @name partials
|
||||||
* @tags fx, superdough
|
* @tags superdough
|
||||||
* @param {number[] | Pattern} magnitudes List of [0, 1] magnitudes for partials. 0th entry is the fundamental harmonic (i.e. DC offset is skipped)
|
* @param {number[] | Pattern} magnitudes List of [0, 1] magnitudes for partials. 0th entry is the fundamental harmonic (i.e. DC offset is skipped)
|
||||||
* @example
|
* @example
|
||||||
* s("user").seg(16).n(irand(8)).scale("A:major")
|
* s("user").seg(16).n(irand(8)).scale("A:major")
|
||||||
|
|
@ -3889,7 +3913,7 @@ export const partials = (list) => {
|
||||||
* Rotates the harmonics of one of the core synths ('sine', 'tri', 'saw', 'user', ..) by a list of phases
|
* Rotates the harmonics of one of the core synths ('sine', 'tri', 'saw', 'user', ..) by a list of phases
|
||||||
*
|
*
|
||||||
* @name phases
|
* @name phases
|
||||||
* @tags fx, superdough
|
* @tags superdough
|
||||||
* @param {number[] | Pattern} phases List of [0, 1) phases for partials. 0th entry is the fundamental phase (i.e. DC offset is skipped)
|
* @param {number[] | Pattern} phases List of [0, 1) phases for partials. 0th entry is the fundamental phase (i.e. DC offset is skipped)
|
||||||
* @example
|
* @example
|
||||||
* // Phase cancellation
|
* // Phase cancellation
|
||||||
|
|
@ -3911,7 +3935,7 @@ export const phases = (list) => {
|
||||||
* calls and/or in a single .FX(fx1, fx2, ..) call. The fx1, .. are _patterns_ which
|
* calls and/or in a single .FX(fx1, fx2, ..) call. The fx1, .. are _patterns_ which
|
||||||
* establish the controls of the given effect. See examples.
|
* establish the controls of the given effect. See examples.
|
||||||
* @name FX
|
* @name FX
|
||||||
* @tags fx, superdough
|
* @tags superdough
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -3962,7 +3986,7 @@ const _asArrayPattern = (pats) => {
|
||||||
* by wrapping them inside a function in K (see example).
|
* by wrapping them inside a function in K (see example).
|
||||||
*
|
*
|
||||||
* @name K
|
* @name K
|
||||||
* @tags generators, fx, superdough
|
* @tags generators, superdough
|
||||||
* @param {KabelsalatExpression | Function} expr Kabelsalat graph definition
|
* @param {KabelsalatExpression | Function} expr Kabelsalat graph definition
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ export const pickmod = register('pickmod', function (lookup, pat) {
|
||||||
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
|
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
|
||||||
* @example
|
* @example
|
||||||
* note("<c2 d2>(3,8)").s("square")
|
* note("<c2 d2>(3,8)").s("square")
|
||||||
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
|
* .pickF("<0 2> 1", [jux(rev), fast(2), x=>x.lpf(800)])
|
||||||
*/
|
*/
|
||||||
export const pickF = register('pickF', function (lookup, funcs, pat) {
|
export const pickF = register('pickF', function (lookup, funcs, pat) {
|
||||||
return pat.apply(pick(lookup, funcs));
|
return pat.apply(pick(lookup, funcs));
|
||||||
|
|
@ -167,20 +167,22 @@ 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
|
* @tags combiners
|
||||||
* @synonyms pickSqueeze
|
* @synonyms pickSqueeze
|
||||||
* @param {Pattern} pat
|
* @param {Pattern} pat
|
||||||
* @param {*} xs
|
* @param {*} xs
|
||||||
* @returns {Pattern}
|
* @returns {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
|
* let a = s("bd(3,8)")
|
||||||
b: s("cp sd")
|
* let b = s("cp sd")
|
||||||
})
|
* "<a b [a,b]>".inhabit({ a, b })
|
||||||
* @example
|
* @example
|
||||||
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
|
* s("a@2 [a b] a"
|
||||||
*/
|
* .inhabit({a: "bd(3,8)", b: "sd sd"}))
|
||||||
|
* .slow(4)
|
||||||
|
*/
|
||||||
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
|
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
|
||||||
return _pick(lookup, pat, false).squeezeJoin();
|
return _pick(lookup, pat, false).squeezeJoin();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -211,11 +209,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) {
|
||||||
|
|
@ -396,7 +389,6 @@ export function repl({
|
||||||
setcps: setCps,
|
setcps: setCps,
|
||||||
setCpm,
|
setCpm,
|
||||||
setcpm: setCpm,
|
setcpm: setCpm,
|
||||||
compileKabel,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,7 +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
|
* @tags external_io, midi
|
||||||
* @example
|
* @example
|
||||||
* defaultmidimap({ lpf: 74 })
|
* defaultmidimap({ lpf: 74 })
|
||||||
* $: note("c a f e").midi();
|
* $: note("c a f e").midi();
|
||||||
|
|
@ -152,7 +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
|
* @tags external_io, midi
|
||||||
* @example
|
* @example
|
||||||
* midimaps({ mymap: { lpf: 74 } })
|
* midimaps({ mymap: { lpf: 74 } })
|
||||||
* $: note("c a f e")
|
* $: note("c a f e")
|
||||||
|
|
@ -529,7 +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
|
* @tags external_io, midi
|
||||||
* @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)
|
||||||
|
|
@ -577,7 +577,7 @@ export async function midin(input) {
|
||||||
* note durations
|
* note durations
|
||||||
*
|
*
|
||||||
* @name midikeys
|
* @name midikeys
|
||||||
* @tags external_io
|
* @tags external_io, midi
|
||||||
* @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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +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
|
* @tags superdough
|
||||||
* @param {number} Max polyphony. Defaults to 128
|
* @param {number} Max polyphony. Defaults to 128
|
||||||
* @example
|
* @example
|
||||||
* setMaxPolyphony(4)
|
* setMaxPolyphony(4)
|
||||||
|
|
@ -74,7 +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
|
* @tags amplitude, 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
|
||||||
|
|
@ -260,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 {
|
||||||
|
|
@ -306,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;
|
||||||
|
|
@ -441,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: {} };
|
||||||
|
|
|
||||||
|
|
@ -186,7 +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
|
* @tags wavetable
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ function scaleOffset(scale, offset, note) {
|
||||||
* - 5P = perfect fifth
|
* - 5P = perfect fifth
|
||||||
* - 5d = diminished fifth
|
* - 5d = diminished fifth
|
||||||
*
|
*
|
||||||
* @tags music_theory
|
* @tags tonal
|
||||||
* @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
|
||||||
|
|
@ -155,7 +155,7 @@ export const { transpose, trans } = register(['transpose', 'trans'], function tr
|
||||||
*
|
*
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @name scaleTranspose
|
* @name scaleTranspose
|
||||||
* @tags music_theory
|
* @tags tonal
|
||||||
* @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
|
||||||
|
|
@ -246,7 +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
|
* @tags tonal
|
||||||
* @param {string} scale Name of scale
|
* @param {string} scale Name of scale
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @example
|
* @example
|
||||||
|
|
|
||||||
|
|
@ -90,7 +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
|
* @tags tonal
|
||||||
* @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
|
||||||
|
|
@ -134,7 +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
|
* @tags tonal
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @param {string} dictionary which voicing dictionary to use.
|
* @param {string} dictionary which voicing dictionary to use.
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
|
|
@ -159,7 +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
|
* @tags tonal
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @param {octave} octave octave to use
|
* @param {octave} octave octave to use
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
|
|
@ -192,7 +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
|
* @tags tonal
|
||||||
* @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()
|
||||||
|
|
|
||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1037,6 +1037,43 @@ exports[`runs examples > example "anchor" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "anchor" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | anchor:c4 note:60 ]",
|
||||||
|
"[ 1/8 → 1/4 | anchor:c4 note:62 ]",
|
||||||
|
"[ 1/4 → 3/8 | anchor:c4 note:64 ]",
|
||||||
|
"[ 3/8 → 1/2 | anchor:c4 note:65 ]",
|
||||||
|
"[ 1/2 → 5/8 | anchor:c4 note:67 ]",
|
||||||
|
"[ 5/8 → 3/4 | anchor:c4 note:69 ]",
|
||||||
|
"[ 3/4 → 7/8 | anchor:c4 note:71 ]",
|
||||||
|
"[ 7/8 → 1/1 | anchor:c4 note:72 ]",
|
||||||
|
"[ 1/1 → 9/8 | anchor:g4 note:67 ]",
|
||||||
|
"[ 9/8 → 5/4 | anchor:g4 note:68 ]",
|
||||||
|
"[ 5/4 → 11/8 | anchor:g4 note:70 ]",
|
||||||
|
"[ 11/8 → 3/2 | anchor:g4 note:72 ]",
|
||||||
|
"[ 3/2 → 13/8 | anchor:g4 note:73 ]",
|
||||||
|
"[ 13/8 → 7/4 | anchor:g4 note:75 ]",
|
||||||
|
"[ 7/4 → 15/8 | anchor:g4 note:77 ]",
|
||||||
|
"[ 15/8 → 2/1 | anchor:g4 note:79 ]",
|
||||||
|
"[ 2/1 → 17/8 | anchor:c5 note:72 ]",
|
||||||
|
"[ 17/8 → 9/4 | anchor:c5 note:74 ]",
|
||||||
|
"[ 9/4 → 19/8 | anchor:c5 note:76 ]",
|
||||||
|
"[ 19/8 → 5/2 | anchor:c5 note:77 ]",
|
||||||
|
"[ 5/2 → 21/8 | anchor:c5 note:79 ]",
|
||||||
|
"[ 21/8 → 11/4 | anchor:c5 note:81 ]",
|
||||||
|
"[ 11/4 → 23/8 | anchor:c5 note:83 ]",
|
||||||
|
"[ 23/8 → 3/1 | anchor:c5 note:84 ]",
|
||||||
|
"[ 3/1 → 25/8 | anchor:g5 note:79 ]",
|
||||||
|
"[ 25/8 → 13/4 | anchor:g5 note:80 ]",
|
||||||
|
"[ 13/4 → 27/8 | anchor:g5 note:82 ]",
|
||||||
|
"[ 27/8 → 7/2 | anchor:g5 note:84 ]",
|
||||||
|
"[ 7/2 → 29/8 | anchor:g5 note:85 ]",
|
||||||
|
"[ 29/8 → 15/4 | anchor:g5 note:87 ]",
|
||||||
|
"[ 15/4 → 31/8 | anchor:g5 note:89 ]",
|
||||||
|
"[ 31/8 → 4/1 | anchor:g5 note:91 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "apply" example index 0 1`] = `
|
exports[`runs examples > example "apply" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | note:C3 ]",
|
"[ 0/1 → 1/1 | note:C3 ]",
|
||||||
|
|
@ -4193,6 +4230,27 @@ exports[`runs examples > example "extend" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "fadeTime" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | s:oh end:0.1 fadeTime:0 ]",
|
||||||
|
"[ 1/4 → 1/2 | s:oh end:0.1 fadeTime:0 ]",
|
||||||
|
"[ 1/2 → 3/4 | s:oh end:0.1 fadeTime:0 ]",
|
||||||
|
"[ 3/4 → 1/1 | s:oh end:0.1 fadeTime:0 ]",
|
||||||
|
"[ 1/1 → 5/4 | s:oh end:0.1 fadeTime:0.2 ]",
|
||||||
|
"[ 5/4 → 3/2 | s:oh end:0.1 fadeTime:0.2 ]",
|
||||||
|
"[ 3/2 → 7/4 | s:oh end:0.1 fadeTime:0.2 ]",
|
||||||
|
"[ 7/4 → 2/1 | s:oh end:0.1 fadeTime:0.2 ]",
|
||||||
|
"[ 2/1 → 9/4 | s:oh end:0.1 fadeTime:0.4 ]",
|
||||||
|
"[ 9/4 → 5/2 | s:oh end:0.1 fadeTime:0.4 ]",
|
||||||
|
"[ 5/2 → 11/4 | s:oh end:0.1 fadeTime:0.4 ]",
|
||||||
|
"[ 11/4 → 3/1 | s:oh end:0.1 fadeTime:0.4 ]",
|
||||||
|
"[ 3/1 → 13/4 | s:oh end:0.1 fadeTime:0.8 ]",
|
||||||
|
"[ 13/4 → 7/2 | s:oh end:0.1 fadeTime:0.8 ]",
|
||||||
|
"[ 7/2 → 15/4 | s:oh end:0.1 fadeTime:0.8 ]",
|
||||||
|
"[ 15/4 → 4/1 | s:oh end:0.1 fadeTime:0.8 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fanchor" example index 0 1`] = `
|
exports[`runs examples > example "fanchor" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | note:f s:sawtooth cutoff:1000 lpenv:8 fanchor:0 ]",
|
"[ 0/1 → 1/8 | note:f s:sawtooth cutoff:1000 lpenv:8 fanchor:0 ]",
|
||||||
|
|
@ -8099,6 +8157,48 @@ exports[`runs examples > example "panchor" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "panspan" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | s:bd pan:0.5 panspan:0 ]",
|
||||||
|
"[ 1/4 → 1/2 | s:hh pan:0.5 panspan:0 ]",
|
||||||
|
"[ 1/2 → 3/4 | s:bd pan:0.5 panspan:0 ]",
|
||||||
|
"[ 3/4 → 1/1 | s:hh pan:0.5 panspan:0 ]",
|
||||||
|
"[ 1/1 → 5/4 | s:bd pan:1 panspan:0.5 ]",
|
||||||
|
"[ 5/4 → 3/2 | s:hh pan:1 panspan:0.5 ]",
|
||||||
|
"[ 3/2 → 7/4 | s:bd pan:1 panspan:0.5 ]",
|
||||||
|
"[ 7/4 → 2/1 | s:hh pan:1 panspan:0.5 ]",
|
||||||
|
"[ 2/1 → 9/4 | s:bd pan:0.5 panspan:1 ]",
|
||||||
|
"[ 9/4 → 5/2 | s:hh pan:0.5 panspan:1 ]",
|
||||||
|
"[ 5/2 → 11/4 | s:bd pan:0.5 panspan:1 ]",
|
||||||
|
"[ 11/4 → 3/1 | s:hh pan:0.5 panspan:1 ]",
|
||||||
|
"[ 3/1 → 13/4 | s:bd pan:0 panspan:0 ]",
|
||||||
|
"[ 13/4 → 7/2 | s:hh pan:0 panspan:0 ]",
|
||||||
|
"[ 7/2 → 15/4 | s:bd pan:0 panspan:0 ]",
|
||||||
|
"[ 15/4 → 4/1 | s:hh pan:0 panspan:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "pansplay" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | s:bd pan:0.5 pansplay:0 ]",
|
||||||
|
"[ 1/4 → 1/2 | s:hh pan:0.5 pansplay:0 ]",
|
||||||
|
"[ 1/2 → 3/4 | s:bd pan:0.5 pansplay:0 ]",
|
||||||
|
"[ 3/4 → 1/1 | s:hh pan:0.5 pansplay:0 ]",
|
||||||
|
"[ 1/1 → 5/4 | s:bd pan:1 pansplay:0.5 ]",
|
||||||
|
"[ 5/4 → 3/2 | s:hh pan:1 pansplay:0.5 ]",
|
||||||
|
"[ 3/2 → 7/4 | s:bd pan:1 pansplay:0.5 ]",
|
||||||
|
"[ 7/4 → 2/1 | s:hh pan:1 pansplay:0.5 ]",
|
||||||
|
"[ 2/1 → 9/4 | s:bd pan:0.5 pansplay:1 ]",
|
||||||
|
"[ 9/4 → 5/2 | s:hh pan:0.5 pansplay:1 ]",
|
||||||
|
"[ 5/2 → 11/4 | s:bd pan:0.5 pansplay:1 ]",
|
||||||
|
"[ 11/4 → 3/1 | s:hh pan:0.5 pansplay:1 ]",
|
||||||
|
"[ 3/1 → 13/4 | s:bd pan:0 pansplay:0 ]",
|
||||||
|
"[ 13/4 → 7/2 | s:hh pan:0 pansplay:0 ]",
|
||||||
|
"[ 7/2 → 15/4 | s:bd pan:0 pansplay:0 ]",
|
||||||
|
"[ 15/4 → 4/1 | s:hh pan:0 pansplay:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "partials" example index 0 1`] = `
|
exports[`runs examples > example "partials" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/16 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
|
"[ 0/1 → 1/16 | s:user note:A3 partials:[1 0 1 0 0 1] ]",
|
||||||
|
|
@ -10874,6 +10974,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 ]",
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Loader from '@src/repl/components/Loader';
|
import Loader from '@src/repl/components/Loader';
|
||||||
import { HorizontalPanel } from '@src/repl/components/panel/Panel';
|
import { BottomPanel } from '@src/repl/components/panel/Panel';
|
||||||
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';
|
||||||
|
|
@ -20,7 +20,7 @@ export default function UdelsEditor(Props) {
|
||||||
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
||||||
</div>
|
</div>
|
||||||
<UserFacingErrorMessage error={error} />
|
<UserFacingErrorMessage error={error} />
|
||||||
<HorizontalPanel context={context} />
|
<BottomPanel context={context} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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 { BottomPanel, MainPanel, RightPanel } 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 h-full">
|
||||||
|
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
||||||
|
{!isZen && panelPosition === 'right' && <RightPanel context={context} />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UserFacingErrorMessage error={error} />
|
<UserFacingErrorMessage error={error} />
|
||||||
{!isZen && panelPosition === 'bottom' && <HorizontalPanel context={context} />}
|
{!isZen && panelPosition === 'bottom' && <BottomPanel 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>}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
website/src/repl/components/icons/SidebarIcon.jsx
Normal file
19
website/src/repl/components/icons/SidebarIcon.jsx
Normal file
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
website/src/repl/components/icons/StrudelIcon.jsx
Normal file
13
website/src/repl/components/icons/StrudelIcon.jsx
Normal file
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,23 @@ import cx from '@src/cx.mjs';
|
||||||
import { useSettings } from '../../../settings.mjs';
|
import { useSettings } from '../../../settings.mjs';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { $strudel_log_history } from '../useLogger';
|
import { $strudel_log_history } from '../useLogger';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
export function ConsoleTab() {
|
export function ConsoleTab() {
|
||||||
const log = useStore($strudel_log_history);
|
const log = useStore($strudel_log_history);
|
||||||
const { fontFamily } = useSettings();
|
const { fontFamily } = useSettings();
|
||||||
|
const scrollRef = useRef();
|
||||||
|
// scroll to bottom when log changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
}, [log]);
|
||||||
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" ref={scrollRef}>
|
||||||
|
{' '}
|
||||||
|
{/* 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;
|
||||||
|
|
@ -16,12 +26,13 @@ export function ConsoleTab() {
|
||||||
<div
|
<div
|
||||||
key={l.id}
|
key={l.id}
|
||||||
className={cx(
|
className={cx(
|
||||||
|
'whitespace-nowrap',
|
||||||
l.type === 'error' ? 'text-background bg-foreground' : 'text-foreground',
|
l.type === 'error' ? 'text-background bg-foreground' : 'text-foreground',
|
||||||
l.type === 'highlight' && 'underline',
|
l.type === 'highlight' && 'underline',
|
||||||
)}
|
)}
|
||||||
style={color ? { color } : {}}
|
style={color ? { color } : {}}
|
||||||
>
|
>
|
||||||
<span dangerouslySetInnerHTML={{ __html: message }} />
|
<span dangerouslySetInnerHTML={{ __html: message }} className="whitespace-nowrap" />
|
||||||
{l.count ? ` (${l.count})` : ''}
|
{l.count ? ` (${l.count})` : ''}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,204 @@
|
||||||
|
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(
|
||||||
|
'flex-none text-black z-[100] text-sm select-none min-h-10 max-h-10',
|
||||||
|
!isZen && !isEmbedded && 'border-b border-muted bg-lineHighlight',
|
||||||
|
isZen ? 'h-12 w-8 fixed top-0 left-0' : '',
|
||||||
|
'flex items-center',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
style={{ fontFamily }}
|
||||||
|
>
|
||||||
|
<div className={cx('flex w-full justify-between')}>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{!isZen && (
|
||||||
|
<div className="flex grow justify-end">
|
||||||
|
{!isButtonRowHidden && <MainMenu isEmbedded={isEmbedded} context={context} />}
|
||||||
|
<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">
|
||||||
|
<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')}
|
||||||
|
>
|
||||||
|
<span className={cx('flex items-center space-x-2')}>
|
||||||
|
{started ? <StopIcon className="w-5 h-5" /> : <PlayIcon className="w-5 h-5" />}
|
||||||
|
{!isEmbedded && <span>{pending ? '...' : started ? 'stop' : 'play'}</span>}
|
||||||
|
</span>
|
||||||
|
</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('px-2 py-0 text-foreground hover:opacity-50')}
|
||||||
|
aria-label="Close Menu"
|
||||||
|
>
|
||||||
|
<XMarkIcon className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BottomPanel({ 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} className={cx(isPanelOpen && 'border-l border-muted')} />
|
||||||
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VerticalPanel({ context }) {
|
export function RightPanel({ 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 h-full overflow-hidden',
|
||||||
{isPanelOpen ? (
|
isPanelOpen ? `min-w-[min(600px,100vw)] max-w-[min(600px,80vw)]` : 'min-w-12 max-w-12',
|
||||||
<div className={cx('flex flex-col h-full')}>
|
|
||||||
<div className="flex justify-between w-full ">
|
|
||||||
<Tabs setTab={setTab} tab={tab} />
|
|
||||||
<PanelActionButton settings={settings} />
|
|
||||||
</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">
|
||||||
|
<PanelCloseButton />
|
||||||
|
<Tabs setTab={setTab} tab={tab} className="border-l border-muted" />
|
||||||
|
</div>
|
||||||
|
<div className="overflow-auto h-full">
|
||||||
|
<PanelContent context={context} tab={tab} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</PanelNav>
|
</PanelNav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -89,8 +216,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 +225,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 +262,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 +271,36 @@ 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 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="menu"
|
||||||
)}
|
className={cx('border-l border-muted px-2 py-0 text-foreground hover:opacity-50')}
|
||||||
aria-label="Pin Menu Panel"
|
onClick={() => setIsPanelOpened(!isPanelOpen)}
|
||||||
>
|
|
||||||
<svg
|
|
||||||
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>
|
<Bars3Icon className="w-6 h-6" />
|
||||||
</svg>
|
</button>
|
||||||
</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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { memo, useEffect, useMemo, useState, Fragment } 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, tags = [] }) => {
|
||||||
|
const isSupradoughOnly = tags.includes('supradough') && !tags.includes('superdough');
|
||||||
|
const isSuperdirtOnly = tags.includes('superdirt') && !tags.includes('superdough');
|
||||||
|
return name && !name.startsWith('_') && !!description && !isSupradoughOnly && !isSuperdirtOnly;
|
||||||
|
};
|
||||||
|
|
||||||
const availableFunctions = (() => {
|
const availableFunctions = (() => {
|
||||||
const seen = new Set(); // avoid repetition
|
const seen = new Set(); // avoid repetition
|
||||||
|
|
@ -13,30 +18,39 @@ const availableFunctions = (() => {
|
||||||
if (seen.has(doc.name)) 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
|
// 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.
|
// @something is. We only want data from comments like `@tags superdough` here.
|
||||||
// If nothing is specified, we default to "untagged" for debugging
|
// If nothing is specified, we default to "untagged" for debugging
|
||||||
doc.tags = doc.tags?.filter((t) => t && typeof t === 'string') || ['untagged'];
|
doc.tags = doc.tags?.filter((t) => t && typeof t === 'string') || ['untagged'];
|
||||||
functions.push(doc);
|
|
||||||
|
|
||||||
const synonyms = doc.synonyms || [];
|
const synonyms = doc.synonyms || [];
|
||||||
|
let names = [doc.name];
|
||||||
seen.add(doc.name);
|
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;
|
||||||
|
names.push(s);
|
||||||
seen.add(s);
|
seen.add(s);
|
||||||
// Swap `doc.name` in for `s` in the list of synonyms
|
|
||||||
const synonymsWithDoc = [doc.name, ...synonyms].filter((x) => x && x !== s);
|
|
||||||
functions.push({
|
|
||||||
...doc,
|
|
||||||
name: s, // update names for the synonym
|
|
||||||
longname: s,
|
|
||||||
synonyms: synonymsWithDoc,
|
|
||||||
synonyms_text: synonymsWithDoc.join(', '),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
doc.allNames = names.join(' ');
|
||||||
|
doc.synonyms = names.slice(1);
|
||||||
|
functions.push(doc);
|
||||||
}
|
}
|
||||||
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 ignoredTags = ['supradough', 'superdirt'];
|
||||||
|
// 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 && !ignoredTags.includes(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;
|
||||||
|
|
@ -45,40 +59,38 @@ const getInnerText = (html) => {
|
||||||
|
|
||||||
export const Reference = memo(function Reference() {
|
export const Reference = memo(function Reference() {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [selectedTag, setSelectedTag] = useState(null);
|
|
||||||
const [selectedFunction, setSelectedFunction] = useState(null);
|
const [selectedFunction, setSelectedFunction] = useState(null);
|
||||||
|
const { referenceTag } = useSettings();
|
||||||
|
|
||||||
const toggleTag = (tag) => {
|
const toggleTag = (tag) => {
|
||||||
if (selectedTag === tag) {
|
if (referenceTag === tag) {
|
||||||
setSelectedTag(null);
|
setReferenceTag('all');
|
||||||
} else {
|
} else {
|
||||||
setSelectedTag(tag);
|
setReferenceTag(tag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchVisibleFunctions = useMemo(() => {
|
const searchVisibleFunctions = useMemo(() => {
|
||||||
return availableFunctions.filter((entry) => {
|
return availableFunctions.filter((entry) => {
|
||||||
if (selectedTag) {
|
if (referenceTag && referenceTag !== 'all') {
|
||||||
if (!(entry.tags || ['untagged']).includes(selectedTag)) {
|
if (!(entry.tags || ['untagged']).includes(referenceTag)) {
|
||||||
return false;
|
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.allNames || entry.name).toLowerCase().includes(lowerCaseSearch) ||
|
||||||
(entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false)
|
(entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [search, selectedTag]);
|
}, [search, referenceTag]);
|
||||||
|
|
||||||
const detailVisibleFunctions = useMemo(() => {
|
const detailVisibleFunctions = useMemo(() => {
|
||||||
return searchVisibleFunctions.filter((x) => {
|
return searchVisibleFunctions.filter((x) => {
|
||||||
if (selectedTag === null) {
|
if (referenceTag === null || referenceTag === 'all') {
|
||||||
if (search) {
|
if (search) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -87,19 +99,10 @@ export const Reference = memo(function Reference() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [searchVisibleFunctions, selectedFunction, selectedTag]);
|
}, [searchVisibleFunctions, selectedFunction, referenceTag]);
|
||||||
|
|
||||||
const tagCounts = {};
|
|
||||||
for (const doc of availableFunctions) {
|
|
||||||
(doc.tags || ['untagged']).forEach((t) => {
|
|
||||||
if (typeof t === 'string' && t) {
|
|
||||||
tagCounts[t] = (tagCounts[t] || 0) + 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSearchTagFilterClick = () => {
|
const onSearchTagFilterClick = () => {
|
||||||
setSelectedTag(null);
|
setReferenceTag('all');
|
||||||
setSelectedFunction(null);
|
setSelectedFunction(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -111,36 +114,44 @@ export const Reference = memo(function Reference() {
|
||||||
}
|
}
|
||||||
}, [selectedFunction]);
|
}, [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 text-foreground flex flex-col gap-3 w-1/3 ">
|
<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
|
<Textbox
|
||||||
className="w-full"
|
className="w-full border-0 border-b border-muted"
|
||||||
placeholder="Search"
|
placeholder="Search..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedFunction(null);
|
setSelectedFunction(null);
|
||||||
|
setReferenceTag('all');
|
||||||
setSearch(e);
|
setSearch(e);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{selectedTag && (
|
|
||||||
|
{/* <div className="flex shrink-0 flex-wrap w-full overflow-auto border-y border-muted">
|
||||||
|
<ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}></ButtonGroup>
|
||||||
|
</div> */}
|
||||||
|
{referenceTag && referenceTag !== 'all' && (
|
||||||
<div className="w-72">
|
<div className="w-72">
|
||||||
<span
|
<span
|
||||||
className="text-foreground border-2 border-gray-500 px-1 py-0.5 my-2 rounded-md cursor-pointer font-sans"
|
className="text-foreground border border-muted border-t-0 border-l-0 px-1 py-0.5 my-2 cursor-pointer font-sans"
|
||||||
onClick={onSearchTagFilterClick}
|
onClick={onSearchTagFilterClick}
|
||||||
>
|
>
|
||||||
{selectedTag}
|
{referenceTag}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
|
<div className="h-full p-2 overflow-y-auto bg-opacity-50">
|
||||||
{searchVisibleFunctions.map((entry, i) => (
|
{searchVisibleFunctions.map((entry, i) => (
|
||||||
<Fragment key={`entry-${entry.name}`}>
|
<Fragment key={`entry-${entry.name}`}>
|
||||||
<a
|
<a
|
||||||
className={
|
className={
|
||||||
'cursor-pointer flex-none hover:bg-lineHighlight overflow-x-hidden px-1 text-ellipsis ' +
|
'whitespace-nowrap cursor-pointer hover:opacity-50 text-ellipsis block' +
|
||||||
(entry.name === selectedFunction ? 'bg-lineHighlight font-bold' : '')
|
(entry.name === selectedFunction ? 'bg-lineHighlight font-bold' : '')
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -151,31 +162,32 @@ export const Reference = memo(function Reference() {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{entry.name}
|
{entry.name} {entry.synonyms && <small className="opacity-50">{entry.synonyms?.join(', ')}</small>}
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="break-normal flex-col overflow-y-auto overflow-x-hidden p-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 className="font-sans text-md">
|
<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>
|
||||||
<div>
|
<div>
|
||||||
|
{/* <ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}/>*/}
|
||||||
{Object.entries(tagCounts)
|
{Object.entries(tagCounts)
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
.map(([t, count]) => (
|
.map(([t, count]) => (
|
||||||
<span key={t}>
|
<span key={t}>
|
||||||
<a
|
<a
|
||||||
className={[
|
className={[
|
||||||
'select-none text-white border-2 border-gray-500 px-1 py-0.5 my-2 cursor-pointer text-sm/8 rounded-md no-underline font-sans',
|
'select-none text-white border border-muted px-1 py-0.5 my-2 cursor-pointer text-sm/8 no-underline font-sans',
|
||||||
`${selectedTag === t ? 'bg-gray-500 text-black' : ''}`,
|
`${referenceTag === t ? 'bg-muted text-foreground' : ''}`,
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
onClick={() => toggleTag(t)}
|
onClick={() => toggleTag(t)}
|
||||||
>
|
>
|
||||||
|
|
@ -187,12 +199,12 @@ export const Reference = memo(function Reference() {
|
||||||
{detailVisibleFunctions.map((entry, i) => (
|
{detailVisibleFunctions.map((entry, i) => (
|
||||||
<section key={i} className="font-sans">
|
<section key={i} className="font-sans">
|
||||||
<div className="flex flex-row items-center mt-8 justify-between">
|
<div className="flex flex-row items-center mt-8 justify-between">
|
||||||
<h3 className="font-mono my-0" id={`doc-${entry.name}`}>
|
<h3 className="font-mono my-0 pt-4" id={`doc-${entry.name}`}>
|
||||||
{entry.name}
|
{entry.name}
|
||||||
</h3>
|
</h3>
|
||||||
{entry.tags && (
|
{entry.tags && (
|
||||||
<span className="ml-2 text-xs text-gray-400 border-2 border-gray-500 rounded-md px-1 py-0.5">
|
<span className="ml-2 text-xs text-foreground border border-muted px-1 py-0.5">
|
||||||
{entry.tags.join(', ')}
|
{entry.tags.filter((t) => !ignoredTags.includes(t)).join(', ')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -201,7 +213,7 @@ export const Reference = memo(function Reference() {
|
||||||
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) => (
|
||||||
|
|
@ -211,12 +223,12 @@ export const Reference = memo(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">Searcb or select a tag to get started.</p>}
|
)) || <p className="font-sans">Search or select a tag to get started.</p>}
|
||||||
{detailVisibleFunctions.length > 0 && <div className="h-screen" />}
|
{detailVisibleFunctions.length > 0 && <div className="h-screen" />}
|
||||||
</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,
|
||||||
|
|
@ -119,7 +155,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
|
||||||
|
|
@ -179,7 +215,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}
|
||||||
|
|
@ -220,13 +256,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"
|
||||||
|
|
@ -300,7 +329,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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -21,7 +21,7 @@ function getUpdatedLog(log, event) {
|
||||||
} else {
|
} else {
|
||||||
log = log.concat([{ message, type, id, data }]);
|
log = log.concat([{ message, type, id, data }]);
|
||||||
}
|
}
|
||||||
return log.slice(-20);
|
return log.slice(-40);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLogger() {
|
export function useLogger() {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { persistentMap } from '@nanostores/persistent';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { register } from '@strudel/core';
|
import { register } from '@strudel/core';
|
||||||
import { isUdels } from './repl/util.mjs';
|
import { isUdels } from './repl/util.mjs';
|
||||||
|
import { computed } from 'nanostores';
|
||||||
|
|
||||||
export const audioEngineTargets = {
|
export const audioEngineTargets = {
|
||||||
webaudio: 'webaudio',
|
webaudio: 'webaudio',
|
||||||
|
|
@ -39,12 +40,12 @@ export const defaultSettings = {
|
||||||
latestCode: '',
|
latestCode: '',
|
||||||
isZen: false,
|
isZen: false,
|
||||||
soundsFilter: soundFilterType.ALL,
|
soundsFilter: soundFilterType.ALL,
|
||||||
|
referenceTag: 'all',
|
||||||
patternFilter: 'community',
|
patternFilter: 'community',
|
||||||
// panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
|
// panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
|
||||||
panelPosition: 'right',
|
panelPosition: 'right',
|
||||||
isPanelPinned: false,
|
isPanelPinned: false,
|
||||||
isPanelOpen: true,
|
isPanelOpen: true,
|
||||||
togglePanelTrigger: 'click', //click | hover
|
|
||||||
userPatterns: '{}',
|
userPatterns: '{}',
|
||||||
prebakeScript: '',
|
prebakeScript: '',
|
||||||
audioEngineTarget: audioEngineTargets.webaudio,
|
audioEngineTarget: audioEngineTargets.webaudio,
|
||||||
|
|
@ -65,11 +66,7 @@ const settings_key = `strudel-settings${instance > 0 ? instance : ''}`;
|
||||||
|
|
||||||
export const settingsMap = persistentMap(settings_key, defaultSettings);
|
export const settingsMap = persistentMap(settings_key, defaultSettings);
|
||||||
|
|
||||||
export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
export const $settings = computed(settingsMap, (state) => {
|
||||||
|
|
||||||
export function useSettings() {
|
|
||||||
const state = useStore(settingsMap);
|
|
||||||
|
|
||||||
const userPatterns = JSON.parse(state.userPatterns);
|
const userPatterns = JSON.parse(state.userPatterns);
|
||||||
Object.keys(userPatterns).forEach((key) => {
|
Object.keys(userPatterns).forEach((key) => {
|
||||||
const data = userPatterns[key];
|
const data = userPatterns[key];
|
||||||
|
|
@ -107,6 +104,12 @@ export function useSettings() {
|
||||||
? true
|
? true
|
||||||
: parseBoolean(state.patternAutoStart),
|
: parseBoolean(state.patternAutoStart),
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
||||||
|
|
||||||
|
export function useSettings() {
|
||||||
|
return useStore($settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { atom } from 'nanostores';
|
import { atom, computed } from 'nanostores';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { logger } from '@strudel/core';
|
import { logger } from '@strudel/core';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
@ -36,12 +36,9 @@ export let $viewingPatternData = sessionAtom('viewingPatternData', {
|
||||||
created_at: Date.now(),
|
created_at: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getViewingPatternData = () => {
|
const $viewingPatterns = computed($viewingPatternData, (state) => parseJSON(state));
|
||||||
return parseJSON($viewingPatternData.get());
|
export const useViewingPatternData = () => useStore($viewingPatterns);
|
||||||
};
|
export const getViewingPatternData = () => $viewingPatterns.get();
|
||||||
export const useViewingPatternData = () => {
|
|
||||||
return useStore($viewingPatternData);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setViewingPatternData = (data) => {
|
export const setViewingPatternData = (data) => {
|
||||||
$viewingPatternData.set(JSON.stringify(data));
|
$viewingPatternData.set(JSON.stringify(data));
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ module.exports = {
|
||||||
background: 'var(--background)',
|
background: 'var(--background)',
|
||||||
lineBackground: 'var(--lineBackground)',
|
lineBackground: 'var(--lineBackground)',
|
||||||
foreground: 'var(--foreground)',
|
foreground: 'var(--foreground)',
|
||||||
|
muted: 'var(--muted)',
|
||||||
caret: 'var(--caret)',
|
caret: 'var(--caret)',
|
||||||
selection: 'var(--selection)',
|
selection: 'var(--selection)',
|
||||||
selectionMatch: 'var(--selectionMatch)',
|
selectionMatch: 'var(--selectionMatch)',
|
||||||
|
|
@ -46,28 +47,28 @@ module.exports = {
|
||||||
'code::after': {
|
'code::after': {
|
||||||
content: 'none',
|
content: 'none',
|
||||||
},
|
},
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
a: {
|
a: {
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
},
|
},
|
||||||
h1: {
|
h1: {
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
},
|
},
|
||||||
h2: {
|
h2: {
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
},
|
},
|
||||||
h3: {
|
h3: {
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
},
|
},
|
||||||
h4: {
|
h4: {
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
},
|
},
|
||||||
pre: {
|
pre: {
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
background: 'var(--background)',
|
background: 'var(--background) !important',
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
color: 'var(--foreground)',
|
color: 'var(--foreground) !important',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue