Merge remote-tracking branch 'upstream/main' into pickr-pickrmod

This commit is contained in:
eefano 2024-02-25 14:52:12 +01:00
commit 74ef157fa1
33 changed files with 163 additions and 89 deletions

View file

@ -2,7 +2,7 @@ import { closeBrackets } from '@codemirror/autocomplete';
// import { search, highlightSelectionMatches } from '@codemirror/search';
import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from '@codemirror/language';
import { Compartment, EditorState, Prec } from '@codemirror/state';
import {
EditorView,
@ -24,6 +24,7 @@ import { persistentAtom } from '@nanostores/persistent';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []),
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
theme,
isAutoCompletionEnabled,
@ -37,6 +38,7 @@ const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [ke
export const defaultSettings = {
keybindings: 'codemirror',
isBracketMatchingEnabled: false,
isLineNumbersDisplayed: true,
isActiveLineHighlighted: false,
isAutoCompletionEnabled: false,
@ -290,6 +292,9 @@ export class StrudelMirror {
setLineWrappingEnabled(enabled) {
this.reconfigureExtension('isLineWrappingEnabled', enabled);
}
setBracketMatchingEnabled(enabled) {
this.reconfigureExtension('isBracketMatchingEnabled', enabled);
}
setLineNumbersDisplayed(enabled) {
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
}

View file

@ -292,7 +292,7 @@ const generic_params = [
* @name begin
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
* @example
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/Dirt-Samples/master/')
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/dirt-samples')
* s("rave").begin("<0 .25 .5 .75>").fast(2)
*
*/

View file

@ -2322,10 +2322,10 @@ const _loopAt = function (factor, pat, cps = 0.5) {
* @memberof Pattern
* @returns Pattern
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* samples('github:tidalcycles/dirt-samples')
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(0.75)
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* samples('github:tidalcycles/dirt-samples')
* s("breaks125").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
*/
@ -2351,7 +2351,7 @@ export const slice = register(
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
* @name splice
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* samples('github:tidalcycles/dirt-samples')
* s("breaks165")
* .splice(8, "0 1 [2 3 0]@2 3 0@2 7")
*/

View file

@ -6,11 +6,11 @@ const OFF_MESSAGE = 0x80;
const CC_MESSAGE = 0xb0;
Pattern.prototype.midi = function (output) {
return this.onTrigger((time, hap, currentTime) => {
return this.onTrigger((time, hap, currentTime, cps) => {
const { note, nrpnn, nrpv, ccn, ccv } = hap.value;
const offset = (time - currentTime) * 1000;
const velocity = Math.floor((hap.context?.velocity ?? 0.9) * 100); // TODO: refactor velocity
const duration = Math.floor(hap.duration.valueOf() * 1000 - 10);
const duration = Math.floor((hap.duration.valueOf() / cps) * 1000 - 10);
const roundedOffset = Math.round(offset);
const midichan = (hap.value.midichan ?? 1) - 1;
const requestedport = output ?? 'IAC';

View file

@ -129,7 +129,7 @@ Pattern.prototype.midi = function (output) {
const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity
// note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length
const duration = Math.floor(hap.duration.valueOf() * 1000 - 10);
const duration = Math.floor((hap.duration.valueOf() / cps) * 1000 - 10);
if (note != null) {
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
const midiNote = new Note(midiNumber, { attack: velocity, duration });
@ -167,10 +167,16 @@ let listeners = {};
const refs = {};
export async function midin(input) {
if (isPattern(input)) {
throw new Error(
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`,
);
}
const initial = await enableWebMidi(); // only returns on first init
const device = getDevice(input, WebMidi.inputs);
if (initial) {
if (initial || WebMidi.enabled) {
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
logger(
`Midi enabled! Using "${device.name}". ${

View file

@ -1,10 +1,11 @@
{
"name": "@strudel/repl",
"version": "1.0.1",
"version": "1.0.2",
"description": "Strudel REPL as a Web Component",
"main": "index.mjs",
"module": "index.mjs",
"publishConfig": {
"main": "dist/index.mjs"
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": {
"build": "vite build",

View file

@ -19,7 +19,7 @@ import { superdough, samples, initAudioOnFirstClick, registerSynthSounds } from
const init = Promise.all([
initAudioOnFirstClick(),
samples('github:tidalcycles/Dirt-Samples/master'),
samples('github:tidalcycles/dirt-samples'),
registerSynthSounds(),
]);
@ -148,7 +148,7 @@ The json file is expected to have the same format as described above.
Because it is common to use github for samples, there is a short way to load a sample map from github:
```js
samples('github:tidalcycles/Dirt-Samples/master')
samples('github:tidalcycles/dirt-samples')
```
The format is `github:<user>/<repo>/<branch>`.

View file

@ -99,6 +99,27 @@ export const getLoadedBuffer = (url) => {
return bufferCache[url];
};
function resolveSpecialPaths(base) {
if (base.startsWith('bubo:')) {
const [_, repo] = base.split(':');
base = `github:Bubobubobubobubo/dough-${repo}`;
}
return base;
}
function githubPath(base, subpath = '') {
if (!base.startsWith('github:')) {
throw new Error('expected "github:" at the start of pseudoUrl');
}
let [_, path] = base.split('github:');
path = path.endsWith('/') ? path.slice(0, -1) : path;
if (path.split('/').length === 2) {
// assume main as default branch if none set
path += '/main';
}
return `https://raw.githubusercontent.com/${path}/${subpath}`;
}
export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '') => {
return Object.entries(sampleMap).forEach(([key, value]) => {
if (typeof value === 'string') {
@ -108,15 +129,19 @@ export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '')
throw new Error('wrong sample map format for ' + key);
}
baseUrl = value._base || baseUrl;
const replaceUrl = (v) => (baseUrl + v).replace('github:', 'https://raw.githubusercontent.com/');
baseUrl = resolveSpecialPaths(baseUrl);
if (baseUrl.startsWith('github:')) {
baseUrl = githubPath(baseUrl, '');
}
const fullUrl = (v) => baseUrl + v;
if (Array.isArray(value)) {
//return [key, value.map(replaceUrl)];
value = value.map(replaceUrl);
value = value.map(fullUrl);
} else {
// must be object
value = Object.fromEntries(
Object.entries(value).map(([note, samples]) => {
return [note, (typeof samples === 'string' ? [samples] : samples).map(replaceUrl)];
return [note, (typeof samples === 'string' ? [samples] : samples).map(fullUrl)];
}),
);
}
@ -142,7 +167,7 @@ function getSamplesPrefixHandler(url) {
/**
* Loads a collection of samples to use with `s`
* @example
* samples('github:tidalcycles/Dirt-Samples/master');
* samples('github:tidalcycles/dirt-samples');
* s("[bd ~]*2, [~ hh]*2, ~ sd")
* @example
* samples({
@ -165,18 +190,9 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
if (handler) {
return handler(sampleMap);
}
if (sampleMap.startsWith('bubo:')) {
const [_, repo] = sampleMap.split(':');
sampleMap = `github:Bubobubobubobubo/dough-${repo}`;
}
sampleMap = resolveSpecialPaths(sampleMap);
if (sampleMap.startsWith('github:')) {
let [_, path] = sampleMap.split('github:');
path = path.endsWith('/') ? path.slice(0, -1) : path;
if (path.split('/').length === 2) {
// assume main as default branch if none set
path += '/main';
}
sampleMap = `https://raw.githubusercontent.com/${path}/strudel.json`;
sampleMap = githubPath(sampleMap, 'strudel.json');
}
if (sampleMap.startsWith('shabda:')) {
let [_, path] = sampleMap.split('shabda:');

View file

@ -23,6 +23,9 @@ describe('transpiler', () => {
it('supports top level await', () => {
expect(transpiler("await samples('xxx');", simple).output).toEqual("await samples('xxx');");
});
it('adds await to bare samples call', () => {
expect(transpiler("samples('xxx');", simple).output).toEqual("await samples('xxx');");
});
/* it('parses dynamic imports', () => {
expect(
transpiler("const { default: foo } = await import('https://bar.com/foo.js');", {

View file

@ -1,8 +1,7 @@
import escodegen from 'escodegen';
import { parse } from 'acorn';
import { walk } from 'estree-walker';
import { isNoteWithOctave } from '@strudel/core';
import { getLeafLocations } from '@strudel/mini';
import { parse } from 'acorn';
import escodegen from 'escodegen';
import { walk } from 'estree-walker';
export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
@ -47,6 +46,9 @@ export function transpiler(input, options = {}) {
});
return this.replace(widgetWithLocation(node));
}
if (isBareSamplesCall(node, parent)) {
return this.replace(withAwait(node));
}
},
leave(node, parent, prop, index) {},
});
@ -119,3 +121,14 @@ function widgetWithLocation(node) {
node.callee.name = 'sliderWithID';
return node;
}
function isBareSamplesCall(node, parent) {
return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression';
}
function withAwait(node) {
return {
type: 'AwaitExpression',
argument: node,
};
}

View file

@ -43,7 +43,7 @@ By default, no external samples are loaded, but you can add them like this:
```js
initStrudel({
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
prebake: () => samples('github:tidalcycles/dirt-samples'),
});
document.getElementById('play').addEventListener('click',

View file

@ -1,10 +1,11 @@
{
"name": "@strudel/web",
"version": "1.0.1",
"version": "1.0.3",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"main": "web.mjs",
"module": "web.mjs",
"publishConfig": {
"main": "dist/index.mjs"
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
@ -36,7 +37,8 @@
"@strudel/mini": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
"@strudel/webaudio": "workspace:*",
"@rollup/plugin-replace": "^5.0.5"
},
"devDependencies": {
"vite": "^5.0.10"

View file

@ -1,6 +1,7 @@
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
import replace from '@rollup/plugin-replace';
// https://vitejs.dev/config/
export default defineConfig({
@ -8,11 +9,18 @@ export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'web.mjs'),
formats: ['es'],
fileName: (ext) => ({ es: 'index.mjs' })[ext],
name: 'strudel',
formats: ['es', 'iife'],
fileName: (ext) => ({ es: 'index.mjs', iife: 'index.js' })[ext],
},
rollupOptions: {
external: [...Object.keys(dependencies)],
// external: [...Object.keys(dependencies)],
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
preventAssignment: true,
}),
],
},
target: 'esnext',
},