Merge remote-tracking branch 'upstream/main' into pickr-pickrmod
This commit is contained in:
commit
74ef157fa1
33 changed files with 163 additions and 89 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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}". ${
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>`.
|
||||
|
|
|
|||
|
|
@ -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:');
|
||||
|
|
|
|||
|
|
@ -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');", {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue