Compare commits

..

12 Commits

Author SHA1 Message Date
Felix Roos 070b4364fe fix: codemirror does not vanish anymore on hot reload 2024-01-12 19:31:10 +01:00
Felix Roos a053f40e59 Merge pull request #902 from tidalcycles/build-warnings
fix some build warnings
2024-01-12 18:31:41 +01:00
Felix Roos 9a0459fdd3 move non pages out of pages dir 2024-01-12 18:16:57 +01:00
Felix Roos 46a859736a Merge pull request #891 from daslyfe/remove_jsx_exports_for_vite
prevent vite from complaining about additional exports in jsx files
2024-01-12 18:01:55 +01:00
Felix Roos e4524a2be9 Merge pull request #892 from daslyfe/update_vite
Update Vite version so hot reload works properly with newest pnpm version
2024-01-12 17:05:46 +01:00
Felix Roos f3ee1cc947 Merge pull request #886 from tidalcycles/polymeter-slowcat
support , in < >
2024-01-12 15:09:40 +01:00
Felix Roos f768e1eece Merge pull request #898 from tidalcycles/fix-autocomplete
fix: autocomplete / tooltip code example bug
2024-01-10 13:55:39 +01:00
Felix Roos 7157634db0 fix: autocomplete / tooltip code example bug 2024-01-09 22:34:16 +01:00
Jade (Rose) Rowland 86442adb20 prettier 2024-01-06 15:09:09 -05:00
Jade (Rose) Rowland 5e3a37c9e8 init 2024-01-06 14:55:45 -05:00
Jade (Rose) Rowland 8d325b96ea init 2024-01-06 14:49:52 -05:00
Felix Roos ca3eda8fc7 support , in < > 2024-01-03 21:54:10 +01:00
20 changed files with 696 additions and 577 deletions
+7 -1
View File
@@ -3,6 +3,12 @@ import jsdoc from '../../doc.json';
import { autocompletion } from '@codemirror/autocomplete';
import { h } from './html';
function plaintext(str) {
const div = document.createElement('div');
div.innerText = str;
return div.innerHTML;
}
const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => {
var div = document.createElement('div');
@@ -21,7 +27,7 @@ ${doc.description}
)}
</ul>
<div>
${doc.examples?.map((example) => `<div><pre>${example}</pre></div>`)}
${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)}
</div>
</div>`[0];
/*
-38
View File
@@ -1,38 +0,0 @@
// import { SelectionRange } from '@codemirror/state';
import { RectangleMarker } from '@codemirror/view';
import { layer } from '@codemirror/view';
// currently stuck with: https://discuss.codemirror.net/t/line-background-layer/7666
export const backlayer = layer({
update: (update) => {
return update.docChanged;
},
markers: (view) => {
const offsetTop = 14; // how to know this number ? .view.documentTop is scroll relative..
const offsetLeft = 4; // how to know this number ?
return view.viewportLineBlocks.map((block) => {
const { left } = view.coordsAtPos(block.to);
return new RectangleMarker('cm-backlayer', offsetLeft, block.top + offsetTop, left, block.height);
});
},
});
/* const len = view.state.doc.length;
const markers = RectangleMarker.forRange(
view,
'cm-backlayer',
SelectionRange.fromJSON({
from: 0,
to: len,
anchor: 0,
head: len,
empty: true,
assoc: -1,
bidiLevel: null,
}),
);
console.log('markser', markers);
return markers;
*/
+1 -2
View File
@@ -21,7 +21,6 @@ import { keybindings } from './keybindings.mjs';
import { initTheme, activateTheme, theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs';
import { persistentAtom } from '@nanostores/persistent';
import { backlayer } from './backlayer.mjs';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
@@ -77,7 +76,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
history(),
EditorView.updateListener.of((v) => onChange(v)),
drawSelection({ cursorBlinkRate: 0 }),
backlayer,
Prec.highest(
keymap.of([
{
@@ -325,6 +323,7 @@ export class StrudelMirror {
this.editor.dispatch({ changes });
}
clear() {
this.stop();
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
}
}
-1
View File
@@ -3,4 +3,3 @@ export * from './highlight.mjs';
export * from './flash.mjs';
export * from './slider.mjs';
export * from './themes.mjs';
export * from './backlayer.mjs';
+2 -2
View File
@@ -292,7 +292,7 @@ function peg$parse(input, options) {
var peg$f3 = function(s) { return s };
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
var peg$f5 = function(a) { return a };
var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
var peg$f6 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
var peg$f7 = function(a) { return x => x.options_['weight'] = a };
var peg$f8 = function(a) { return x => x.options_['reps'] = a };
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
@@ -1073,7 +1073,7 @@ function peg$parse(input, options) {
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
s4 = peg$parsesequence();
s4 = peg$parsepolymeter_stack();
if (s4 !== peg$FAILED) {
s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 62) {
+2 -2
View File
@@ -119,8 +119,8 @@ polymeter_steps = "%"a:slice
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
// change the alignment to slowcat
slow_sequence = ws "<" ws s:sequence ws ">" ws
{ s.arguments_.alignment = 'slowcat'; return s; }
slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws
{ s.arguments_.alignment = 'polymeter_slowcat'; return s; }
// a slice is either a single step or a sub cycle
slice = step / sub_cycle / polymeter / slow_sequence
+4 -6
View File
@@ -91,6 +91,10 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
if (alignment === 'stack') {
return strudel.stack(...children);
}
if (alignment === 'polymeter_slowcat') {
const aligned = children.map((child) => child._slow(strudel.Fraction(child.__weight ?? 1)));
return strudel.stack(...aligned);
}
if (alignment === 'polymeter') {
// polymeter
const stepsPerCycle = ast.arguments_.stepsPerCycle
@@ -104,15 +108,9 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 'slowcat') {
return strudel.slowcat(...children);
}
if (weightedChildren) {
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
if (alignment === 'slowcat') {
return pat._slow(weightSum); // timecat + slow
}
pat.__weight = weightSum;
return pat;
}
+603 -456
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { getMetadata } from '../website/src/pages/metadata_parser';
import { getMetadata } from '../website/src/metadata_parser';
describe.concurrent('Metadata parser', () => {
it('loads a tag from inline comment', async () => {
+10
View File
@@ -0,0 +1,10 @@
import { getMetadata } from './metadata_parser';
export function getMyPatterns() {
const my = import.meta.glob('../../my-patterns/**', { as: 'raw', eager: true });
return Object.fromEntries(
Object.entries(my)
.filter(([name]) => name.endsWith('.txt'))
.map(([name, raw]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
);
}
+2 -1
View File
@@ -2,7 +2,7 @@
import * as tunes from '../../../src/repl/tunes.mjs';
import HeadCommon from '../../components/HeadCommon.astro';
import { getMetadata } from '../metadata_parser';
import { getMetadata } from '../../metadata_parser';
const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
@@ -25,3 +25,4 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
}
</div>
</body>
../../metadata_parser
+2 -2
View File
@@ -2,9 +2,9 @@ import { createCanvas } from 'canvas';
import { pianoroll } from '@strudel.cycles/core';
import { evaluate } from '@strudel.cycles/transpiler';
import '../../../../test/runtime.mjs';
import { getMyPatterns } from './list.json';
import { getMyPatterns } from '../../my_patterns';
export async function get({ params, request }) {
export async function GET({ params, request }) {
const patterns = await getMyPatterns();
const { name } = params;
const tune = patterns[name];
+2 -1
View File
@@ -1,5 +1,5 @@
---
import { getMyPatterns } from './list.json';
import { getMyPatterns } from '../../my_patterns.js';
import { Content } from '../../../../my-patterns/README.md';
import HeadCommon from '../../components/HeadCommon.astro';
@@ -37,3 +37,4 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
}
</div>
</body>
../../list.json
-17
View File
@@ -1,17 +0,0 @@
import { getMetadata } from '../metadata_parser';
export function getMyPatterns() {
const my = import.meta.glob('../../../../my-patterns/**', { as: 'raw', eager: true });
return Object.fromEntries(
Object.entries(my)
.filter(([name]) => name.endsWith('.txt'))
.map(([name, raw]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
);
}
export async function get() {
const all = await getMyPatterns();
return {
body: JSON.stringify(all),
};
}
+3 -5
View File
@@ -10,11 +10,6 @@
--gutterForeground: #8a919966;
}
.cm-backlayer {
background: #00ff0050;
/* background: var(--lineBackground) */
}
.darken::before {
content: ' ';
position: fixed;
@@ -35,6 +30,9 @@
#code .cm-content {
padding-bottom: 50vh;
}
#code .cm-line > * {
background: var(--lineBackground);
}
#code .cm-editor {
background-color: transparent !important;
+15 -5
View File
@@ -8,9 +8,10 @@ import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core
import cx from '@src/cx.mjs';
import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
import { defaultAudioDeviceName, getAudioDevices, setAudioDevice } from './panel/AudioDeviceSelector';
import { defaultAudioDeviceName } from '../settings.mjs';
import { getAudioDevices, setAudioDevice } from './util.mjs';
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
initUserCode,
setActivePattern,
@@ -24,12 +25,11 @@ import Loader from './Loader';
import { Panel } from './panel/Panel';
import { useStore } from '@nanostores/react';
import { prebake } from './prebake.mjs';
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
import { getRandomTune, initCode, loadModules, shareCode, ReplContext } from './util.mjs';
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import './Repl.css';
const { code: randomTune, name } = getRandomTune();
export const ReplContext = createContext(null);
const { latestCode } = settingsMap.get();
@@ -100,6 +100,16 @@ export function Repl({ embedded = false }) {
});
editorRef.current = editor;
if (import.meta.hot) {
import.meta.hot.dispose(() => {
editorRef.current.clear();
setTimeout(() => {
delete editorRef.current;
delete containerRef.current;
});
});
}
}, []);
const [replState, setReplState] = useState({});
@@ -199,7 +209,7 @@ export function Repl({ embedded = false }) {
id="code"
ref={(el) => {
containerRef.current = el;
if (!editorRef.current) {
if (!editorRef.current && containerRef.current) {
init();
}
}}
+1 -35
View File
@@ -1,42 +1,8 @@
import React, { useState } from 'react';
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel.cycles/webaudio';
import { getAudioDevices, setAudioDevice } from '../util.mjs';
import { SelectInput } from './SelectInput';
import { logger } from '@strudel.cycles/core';
const initdevices = new Map();
export const defaultAudioDeviceName = 'System Standard';
export const getAudioDevices = async () => {
await navigator.mediaDevices.getUserMedia({ audio: true });
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
const devicesMap = new Map();
devicesMap.set(defaultAudioDeviceName, '');
mediaDevices.forEach((device) => {
devicesMap.set(device.label, device.deviceId);
});
return devicesMap;
};
export const setAudioDevice = async (id) => {
let audioCtx = getAudioContext();
if (audioCtx.sinkId === id) {
return;
}
await audioCtx.suspend();
await audioCtx.close();
audioCtx = setDefaultAudioContext();
await audioCtx.resume();
const isValidID = (id ?? '').length > 0;
if (isValidID) {
try {
await audioCtx.setSinkId(id);
} catch {
logger('failed to set audio interface', 'warning');
}
}
initializeAudioOutput();
};
// Allows the user to select an audio interface for Strudel to play through
export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) {
+38 -1
View File
@@ -1,11 +1,14 @@
import { controls, evalScope, hash2code, logger } from '@strudel.cycles/core';
import { settingPatterns } from '../settings.mjs';
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel.cycles/webaudio';
import { isTauri } from '../tauri.mjs';
import './Repl.css';
import * as tunes from './tunes.mjs';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
import { writeText } from '@tauri-apps/api/clipboard';
import { createContext } from 'react';
// Create a single supabase client for interacting with your database
const supabase = createClient(
@@ -110,3 +113,37 @@ export async function shareCode(codeToShare) {
logger(message);
}
}
export const ReplContext = createContext(null);
export const getAudioDevices = async () => {
await navigator.mediaDevices.getUserMedia({ audio: true });
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
const devicesMap = new Map();
devicesMap.set(defaultAudioDeviceName, '');
mediaDevices.forEach((device) => {
devicesMap.set(device.label, device.deviceId);
});
return devicesMap;
};
export const setAudioDevice = async (id) => {
let audioCtx = getAudioContext();
if (audioCtx.sinkId === id) {
return;
}
await audioCtx.suspend();
await audioCtx.close();
audioCtx = setDefaultAudioContext();
await audioCtx.resume();
const isValidID = (id ?? '').length > 0;
if (isValidID) {
try {
await audioCtx.setSinkId(id);
} catch {
logger('failed to set audio interface', 'warning');
}
}
initializeAudioOutput();
};
+3 -1
View File
@@ -2,9 +2,10 @@ import { persistentMap, persistentAtom } from '@nanostores/persistent';
import { useStore } from '@nanostores/react';
import { register } from '@strudel.cycles/core';
import * as tunes from './repl/tunes.mjs';
import { defaultAudioDeviceName } from './repl/panel/AudioDeviceSelector';
import { logger } from '@strudel.cycles/core';
export const defaultAudioDeviceName = 'System Standard';
export const defaultSettings = {
activeFooter: 'intro',
keybindings: 'codemirror',
@@ -171,6 +172,7 @@ export function updateUserCode(code) {
setActivePattern(example);
return;
}
if (!activePattern) {
// create new user pattern
activePattern = newUserPattern();