mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 13:13:10 -04:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3daacba2c3 | |||
| fa7c16dbe7 | |||
| 6a43eb6755 | |||
| c524ebc7f3 | |||
| 0deb905cdf | |||
| 4cadbcbbdf | |||
| 96eef75f74 | |||
| 51d9e02e07 | |||
| c4c21c4f0c | |||
| fcb247b62a | |||
| 7556da7839 | |||
| eb93a6c149 | |||
| b870ab204a | |||
| f779e81993 | |||
| 5c71bb95a0 | |||
| 1525992d73 | |||
| abe25be05e | |||
| 664a19a5c4 | |||
| 8efadd2547 | |||
| ed1f3e1e8c | |||
| 4aaf9eaed4 | |||
| 0416e4d212 | |||
| b9247db001 | |||
| 74ef157fa1 | |||
| d4724b0e12 | |||
| dbfdbcf2b7 | |||
| 0986b14508 | |||
| 325152c782 | |||
| c136d12194 | |||
| 7557952155 | |||
| b0b41e6718 | |||
| 22e3067b8c | |||
| e9ab01ab06 | |||
| 058e554b0f | |||
| 6d9327aebc | |||
| c23d7850c7 | |||
| 2854f0cf34 | |||
| a89e87ca2e | |||
| 5419ca431b | |||
| e8b674badd | |||
| 8af40a9c61 | |||
| f54aa3d823 | |||
| 5e91287e3e | |||
| 4e3d4a7060 | |||
| 3d80da9816 | |||
| bf63b716af | |||
| 38d389b838 | |||
| ebdca32c80 | |||
| c06a3710c1 | |||
| d5e67fe13f | |||
| 00c7da45c0 | |||
| 3347817350 | |||
| 88ece92a78 | |||
| 03a73fb95e | |||
| 23c55c9636 | |||
| 12c6f4e95f | |||
| a468ddb85b | |||
| 051bdaccc7 | |||
| 5dc04d9574 | |||
| 60129413d3 | |||
| d2bf9370ea | |||
| fd11aa5eec | |||
| fb7c2b61d3 | |||
| 6da98a5f31 |
@@ -6,7 +6,7 @@
|
||||
<script type="module">
|
||||
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
|
||||
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev)`));
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
</div>
|
||||
<div id="output"></div>
|
||||
<script type="module">
|
||||
// TODO: refactor to use newer version without controls import
|
||||
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel/core@0.11.0';
|
||||
import { mini } from 'https://cdn.skypack.dev/@strudel/mini@0.11.0';
|
||||
import { transpiler } from 'https://cdn.skypack.dev/@strudel/transpiler@0.11.0';
|
||||
@@ -52,8 +53,8 @@
|
||||
});
|
||||
|
||||
function getTune() {
|
||||
return `await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
return `samples('github:tidalcycles/dirt-samples')
|
||||
setcps(1);
|
||||
stack(
|
||||
// amen
|
||||
n("0 1 2 3 4 5 6 7")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script src="https://unpkg.com/@strudel/repl@0.9.4"></script>
|
||||
<script src="https://unpkg.com/@strudel/repl@1.0.2"></script>
|
||||
<strudel-editor>
|
||||
<!--
|
||||
// @date 23-08-15
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
<button id="play">PLAY</button>
|
||||
<script>
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
document.getElementById('play').addEventListener('click', () => s('bd sd').play());
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StrudelMirror } from '@strudel/codemirror';
|
||||
import { funk42 } from './tunes';
|
||||
import { drawPianoroll, evalScope, controls } from '@strudel/core';
|
||||
import { drawPianoroll, evalScope } from '@strudel/core';
|
||||
import './style.css';
|
||||
import { initAudioOnFirstClick } from '@strudel/webaudio';
|
||||
import { transpiler } from '@strudel/transpiler';
|
||||
@@ -25,7 +25,6 @@ const editor = new StrudelMirror({
|
||||
prebake: async () => {
|
||||
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
|
||||
const loadModules = evalScope(
|
||||
controls,
|
||||
import('@strudel/core'),
|
||||
import('@strudel/mini'),
|
||||
import('@strudel/tonal'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
|
||||
await samples('github:felixroos/samples/main')
|
||||
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
samples('github:felixroos/samples')
|
||||
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
|
||||
"<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
|
||||
// make it 22 edo
|
||||
@@ -33,8 +33,8 @@ await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tid
|
||||
|
||||
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
|
||||
|
||||
await samples('github:felixroos/samples/main')
|
||||
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
samples('github:felixroos/samples')
|
||||
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
|
||||
addVoicings('hip', {
|
||||
m11: ['2M 3m 4P 7m'],
|
||||
@@ -69,8 +69,8 @@ export const funk42 = `// froos - how to funk in 42 lines of code
|
||||
// adapted from "how to funk in two minutes" by marc rebillet https://www.youtube.com/watch?v=3vBwRfQbXkg
|
||||
// thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
|
||||
|
||||
await samples('github:felixroos/samples/main')
|
||||
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
samples('github:felixroos/samples')
|
||||
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
|
||||
setcps(.5)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script type="module">
|
||||
import { initStrudel } from '@strudel/web';
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
|
||||
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { controls, repl, evalScope } from '@strudel/core';
|
||||
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
|
||||
import { repl, evalScope } from '@strudel/core';
|
||||
import { getAudioContext, webaudioOutput, initAudioOnFirstClick, registerSynthSounds } from '@strudel/webaudio';
|
||||
import { transpiler } from '@strudel/transpiler';
|
||||
import tune from './tune.mjs';
|
||||
|
||||
@@ -7,14 +7,9 @@ const ctx = getAudioContext();
|
||||
const input = document.getElementById('text');
|
||||
input.innerHTML = tune;
|
||||
initAudioOnFirstClick();
|
||||
registerSynthSounds();
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
import('@strudel/core'),
|
||||
import('@strudel/mini'),
|
||||
import('@strudel/webaudio'),
|
||||
import('@strudel/tonal'),
|
||||
);
|
||||
evalScope(import('@strudel/core'), import('@strudel/mini'), import('@strudel/webaudio'), import('@strudel/tonal'));
|
||||
|
||||
const { evaluate } = repl({
|
||||
defaultOutput: webaudioOutput,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default `await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
export default `samples('github:tidalcycles/dirt-samples')
|
||||
setcps(1)
|
||||
stack(
|
||||
// amen
|
||||
n("0 1 2 3 4 5 6 7")
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
const init = Promise.all([
|
||||
initAudioOnFirstClick(),
|
||||
samples('github:tidalcycles/Dirt-Samples/master'),
|
||||
samples('github:tidalcycles/dirt-samples'),
|
||||
registerSynthSounds(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Pattern, getDrawContext, silence, register, pure } from './index.mjs';
|
||||
import controls from './controls.mjs'; // do not import from index.mjs as it breaks for some reason..
|
||||
const { createParams } = controls;
|
||||
import { Pattern, getDrawContext, silence, register, pure, createParams } from './index.mjs';
|
||||
|
||||
let clearColor = '#22222210';
|
||||
|
||||
|
||||
+1445
-1420
File diff suppressed because it is too large
Load Diff
@@ -134,7 +134,7 @@ export class Drawer {
|
||||
this.lastFrame = phase;
|
||||
this.visibleHaps = (this.visibleHaps || [])
|
||||
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
|
||||
.filter((h) => h.whole?.end >= phase - lookbehind - lookahead)
|
||||
.filter((h) => h.endClipped >= phase - lookbehind - lookahead)
|
||||
// add new haps with onset (think right edge bars scrolling in)
|
||||
.concat(haps.filter((h) => h.hasOnset()));
|
||||
const time = phase - lookahead;
|
||||
|
||||
+15
-15
@@ -10,8 +10,9 @@ https://rohandrape.net/?t=hmt
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { timeCat, register, silence } from './pattern.mjs';
|
||||
import { Pattern, timeCat, register, silence } from './pattern.mjs';
|
||||
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
const left = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
@@ -40,11 +41,17 @@ const _bjork = function (n, x) {
|
||||
};
|
||||
|
||||
export const bjork = function (ons, steps) {
|
||||
const inverted = ons < 0;
|
||||
ons = Math.abs(ons);
|
||||
const offs = steps - ons;
|
||||
const x = Array(ons).fill([1]);
|
||||
const y = Array(offs).fill([0]);
|
||||
const result = _bjork([ons, offs], [x, y]);
|
||||
return flatten(result[1][0]).concat(flatten(result[1][1]));
|
||||
const p = flatten(result[1][0]).concat(flatten(result[1][1]));
|
||||
if (inverted) {
|
||||
return p.map((x) => (x === 0 ? 1 : 0));
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -57,7 +64,6 @@ export const bjork = function (ons, steps) {
|
||||
*
|
||||
* @memberof Pattern
|
||||
* @name euclid
|
||||
* @synonyms euc
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @returns Pattern
|
||||
@@ -70,7 +76,6 @@ export const bjork = function (ons, steps) {
|
||||
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
|
||||
* @memberof Pattern
|
||||
* @name euclidRot
|
||||
* @synonyms eucr
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @param {number} rotation offset in steps
|
||||
@@ -133,22 +138,18 @@ const _euclidRot = function (pulses, steps, rotation) {
|
||||
return b;
|
||||
};
|
||||
|
||||
export const euclid = register(['euclid', 'euc'], function (pulses, steps, pat) {
|
||||
export const euclid = register('euclid', function (pulses, steps, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, 0));
|
||||
});
|
||||
|
||||
export const { euclidrot, euclidRot } = register(
|
||||
['euclidrot', 'euclidRot', 'eucr'],
|
||||
function (pulses, steps, rotation, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, rotation));
|
||||
},
|
||||
);
|
||||
export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], function (pulses, steps, rotation, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, rotation));
|
||||
});
|
||||
|
||||
/**
|
||||
* Similar to `euclid`, but each pulse is held until the next pulse,
|
||||
* so there will be no gaps.
|
||||
* @name euclidLegato
|
||||
* @synonyms eucl
|
||||
* @memberof Pattern
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
@@ -169,7 +170,7 @@ const _euclidLegato = function (pulses, steps, rotation, pat) {
|
||||
return pat.struct(timeCat(...gapless));
|
||||
};
|
||||
|
||||
export const euclidLegato = register(['euclidLegato', 'eucl'], function (pulses, steps, pat) {
|
||||
export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) {
|
||||
return _euclidLegato(pulses, steps, 0, pat);
|
||||
});
|
||||
|
||||
@@ -178,7 +179,6 @@ export const euclidLegato = register(['euclidLegato', 'eucl'], function (pulses,
|
||||
* so there will be no gaps, and has an additional parameter for 'rotating'
|
||||
* the resulting sequence
|
||||
* @name euclidLegatoRot
|
||||
* @synonyms euclr
|
||||
* @memberof Pattern
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
@@ -186,6 +186,6 @@ export const euclidLegato = register(['euclidLegato', 'eucl'], function (pulses,
|
||||
* @example
|
||||
* note("c3").euclidLegatoRot(3,5,2)
|
||||
*/
|
||||
export const euclidLegatoRot = register(['euclidLegatoRot', 'euclr'], function (pulses, steps, rotation, pat) {
|
||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||
return _euclidLegato(pulses, steps, rotation, pat);
|
||||
});
|
||||
|
||||
+11
-1
@@ -3,6 +3,7 @@ hap.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/hap.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
export class Hap {
|
||||
/*
|
||||
@@ -32,7 +33,16 @@ export class Hap {
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1);
|
||||
let duration;
|
||||
if (typeof this.value?.duration === 'number') {
|
||||
duration = Fraction(this.value.duration);
|
||||
} else {
|
||||
duration = this.whole.end.sub(this.whole.begin);
|
||||
}
|
||||
if (typeof this.value?.clip === 'number') {
|
||||
return duration.mul(this.value.clip);
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
get endClipped() {
|
||||
|
||||
@@ -4,11 +4,12 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import controls from './controls.mjs';
|
||||
import * as controls from './controls.mjs'; // legacy
|
||||
export * from './euclid.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
export { Fraction, controls };
|
||||
export * from './controls.mjs';
|
||||
export * from './hap.mjs';
|
||||
export * from './pattern.mjs';
|
||||
export * from './signal.mjs';
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
export const logKey = 'strudel.log';
|
||||
|
||||
let debounce = 1000,
|
||||
lastMessage,
|
||||
lastTime;
|
||||
|
||||
export function logger(message, type, data = {}) {
|
||||
let t = performance.now();
|
||||
if (lastMessage === message && t - lastTime < debounce) {
|
||||
return;
|
||||
}
|
||||
lastMessage = message;
|
||||
lastTime = t;
|
||||
console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px');
|
||||
if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') {
|
||||
document.dispatchEvent(
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/core",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
|
||||
@@ -2203,12 +2203,6 @@ export const bypass = register('bypass', function (on, pat) {
|
||||
*/
|
||||
export const ribbon = register('ribbon', (offset, cycles, pat) => pat.early(offset).restart(pure(1).slow(cycles)));
|
||||
|
||||
// sets absolute duration of haps
|
||||
// TODO - fix
|
||||
export const duration = register('duration', function (value, pat) {
|
||||
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
||||
});
|
||||
|
||||
export const hsla = register('hsla', (h, s, l, a, pat) => {
|
||||
return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`);
|
||||
});
|
||||
@@ -2241,21 +2235,6 @@ export const velocity = register('velocity', function (velocity, pat) {
|
||||
return pat.withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity }));
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Multiplies the hap duration with the given factor.
|
||||
* With samples, `clip` might be a better function to use ([more info](https://github.com/tidalcycles/strudel/pull/598))
|
||||
* @name legato
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* note("c3 eb3 g3 c4").legato("<.25 .5 1 2>")
|
||||
*/
|
||||
// TODO - fix
|
||||
export const legato = register('legato', function (value, pat) {
|
||||
value = Fraction(value);
|
||||
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Control-related functions, i.e. ones that manipulate patterns of
|
||||
// objects
|
||||
@@ -2322,10 +2301,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 +2330,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")
|
||||
*/
|
||||
|
||||
@@ -244,9 +244,68 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pickmod(lookup, funcs));
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickOut = register('pickOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).outerJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).outerJoin();
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickRestart = register('pickRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).trigzeroJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).trigzeroJoin();
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickReset = register('pickReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).trigJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).trigJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
/** * 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.
|
||||
* @name inhabit
|
||||
* @synonyms pickSqueeze
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
@@ -257,21 +316,23 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
|
||||
* @example
|
||||
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
|
||||
*/
|
||||
export const inhabit = register('inhabit', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).squeezeJoin();
|
||||
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).squeezeJoin();
|
||||
});
|
||||
|
||||
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* For example, if you pick the fifth pattern of a list of three, you'll get the
|
||||
* second one.
|
||||
* @name inhabitmod
|
||||
* @synonyms pickmodSqueeze
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const inhabitmod = register('inhabit', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).squeezeJoin();
|
||||
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).squeezeJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,23 +4,23 @@ Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import controls from '../controls.mjs';
|
||||
import { s } from '../controls.mjs';
|
||||
import { mini } from '../../mini/mini.mjs';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('controls', () => {
|
||||
it('should support controls', () => {
|
||||
expect(controls.s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
|
||||
expect(s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
|
||||
});
|
||||
it('should support compound controls', () => {
|
||||
expect(controls.s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
|
||||
expect(controls.s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
|
||||
expect(s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
|
||||
expect(s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
|
||||
{ s: 'bd', n: 3 },
|
||||
{ s: 'sd', n: 4, gain: 1.4 },
|
||||
]);
|
||||
});
|
||||
it('should support ignore extra elements in compound controls', () => {
|
||||
expect(controls.s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
|
||||
expect(s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
|
||||
{ s: 'bd', n: 3, gain: 0.4 },
|
||||
{ s: 'sd', n: 4, gain: 0.5 },
|
||||
]);
|
||||
|
||||
@@ -51,9 +51,8 @@ import {
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
|
||||
import controls from '../controls.mjs';
|
||||
import { n, s } from '../controls.mjs';
|
||||
|
||||
const { n, s } = controls;
|
||||
const st = (begin, end) => new State(ts(begin, end));
|
||||
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
|
||||
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
|
||||
|
||||
@@ -6,8 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { map, valued, mul } from '../value.mjs';
|
||||
import controls from '../controls.mjs';
|
||||
const { n } = controls;
|
||||
import { n } from '../controls.mjs';
|
||||
|
||||
describe('Value', () => {
|
||||
it('unionWith', () => {
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/csound",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/hydra",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Hydra integration for strudel",
|
||||
"main": "hydra.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"server": "node server.js",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'hydra.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -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,11 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/midi",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/mini",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/osc",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "OSC messaging for strudel",
|
||||
"main": "osc.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"server": "node server.js",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'osc.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# @strudel/repl
|
||||
|
||||
The Strudel REPL as a web component.
|
||||
|
||||
[Usage example](https://github.com/tidalcycles/strudel/blob/main/examples/buildless/web-component-no-iframe.html)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@strudel/repl",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"description": "Strudel REPL as a Web Component",
|
||||
"main": "index.mjs",
|
||||
"module": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
|
||||
import { noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
|
||||
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio';
|
||||
import * as core from '@strudel/core';
|
||||
|
||||
@@ -17,7 +17,6 @@ export async function prebake() {
|
||||
// import('@strudel/serial'),
|
||||
// import('@strudel/csound'),
|
||||
// import('@strudel/osc'),
|
||||
controls, // sadly, this cannot be exported from core directly (yet)
|
||||
);
|
||||
// load samples
|
||||
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/serial",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Webserial API for strudel",
|
||||
"main": "serial.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'serial.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "@strudel/soundfonts",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Soundsfont support for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -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>`.
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "superdough",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"directories": {
|
||||
"example": "examples"
|
||||
|
||||
@@ -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:');
|
||||
@@ -235,7 +251,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||
nudge = 0, // TODO: is this in seconds?
|
||||
cut,
|
||||
loop,
|
||||
clip = undefined, // if 1, samples will be cut off when the hap ends
|
||||
clip = undefined, // if set, samples will be cut off when the hap ends
|
||||
n = 0,
|
||||
note,
|
||||
speed = 1, // sample playback speed
|
||||
|
||||
@@ -253,6 +253,11 @@ function effectSend(input, effect, wet) {
|
||||
return send;
|
||||
}
|
||||
|
||||
export function resetGlobalEffects() {
|
||||
delays = {};
|
||||
reverbs = {};
|
||||
}
|
||||
|
||||
export const superdough = async (value, deadline, hapDuration) => {
|
||||
const ac = getAudioContext();
|
||||
if (typeof value !== 'object') {
|
||||
@@ -513,6 +518,7 @@ export const superdough = async (value, deadline, hapDuration) => {
|
||||
// toDisconnect = all the node that should be disconnected in onended callback
|
||||
// this is crucial for performance
|
||||
toDisconnect = chain.concat([delaySend, reverbSend, analyserSend]);
|
||||
return { mute: (dt) => post.gain.linearRampToValueAtTime(0, ac.currentTime + dt) };
|
||||
};
|
||||
|
||||
export const superdoughTrigger = (t, hap, ct, cps) => superdough(hap, t - ct, hap.duration / cps, cps);
|
||||
|
||||
@@ -8,7 +8,7 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.cjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "@strudel/tonal",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Tonal functions for strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -7,10 +7,9 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
// import { strict as assert } from 'assert';
|
||||
|
||||
import '../tonal.mjs'; // need to import this to add prototypes
|
||||
import { pure, controls, seq } from '@strudel/core';
|
||||
import { pure, n, seq } from '@strudel/core';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mini } from '../../mini/mini.mjs';
|
||||
const { n } = controls;
|
||||
|
||||
describe('tonal', () => {
|
||||
it('Should run tonal functions ', () => {
|
||||
|
||||
@@ -103,6 +103,8 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
|
||||
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
|
||||
return hap.withValue(() => hap.value + semitones);
|
||||
}
|
||||
if (typeof hap.value === 'object')
|
||||
return hap.withValue(() => ({ ...hap.value, note: Note.simplify(Note.transpose(hap.value.note, interval)) }));
|
||||
// TODO: move simplify to player to preserve enharmonics
|
||||
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
|
||||
return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval)));
|
||||
@@ -133,6 +135,11 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
|
||||
if (!hap.context.scale) {
|
||||
throw new Error('can only use scaleTranspose after .scale');
|
||||
}
|
||||
if (typeof hap.value === 'object')
|
||||
return hap.withValue(() => ({
|
||||
...hap.value,
|
||||
note: scaleOffset(hap.context.scale, Number(offset), hap.value.note),
|
||||
}));
|
||||
if (typeof hap.value !== 'string') {
|
||||
throw new Error('can only use scaleTranspose with notes');
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/transpiler",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -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,8 +1,8 @@
|
||||
{
|
||||
"name": "@strudel/web",
|
||||
"version": "1.0.0",
|
||||
"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.js",
|
||||
"module": "dist/index.mjs"
|
||||
@@ -37,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', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[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',
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export * from '@strudel/transpiler';
|
||||
export * from '@strudel/mini';
|
||||
export * from '@strudel/tonal';
|
||||
export * from '@strudel/webaudio';
|
||||
import { Pattern, evalScope, controls } from '@strudel/core';
|
||||
import { Pattern, evalScope } from '@strudel/core';
|
||||
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel/webaudio';
|
||||
// import { registerSoundfonts } from '@strudel/soundfonts';
|
||||
import { evaluate as _evaluate } from '@strudel/transpiler';
|
||||
@@ -15,7 +15,6 @@ import { miniAllStrings } from '@strudel/mini';
|
||||
export async function defaultPrebake() {
|
||||
const loadModules = evalScope(
|
||||
evalScope,
|
||||
controls,
|
||||
import('@strudel/core'),
|
||||
import('@strudel/mini'),
|
||||
import('@strudel/tonal'),
|
||||
|
||||
@@ -12,9 +12,8 @@ npm i @strudel/webaudio --save
|
||||
## Example
|
||||
|
||||
```js
|
||||
import { repl, controls } from "@strudel/core";
|
||||
import { repl, note } from "@strudel/core";
|
||||
import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel/webaudio";
|
||||
const { note } = controls;
|
||||
|
||||
initAudioOnFirstClick();
|
||||
const ctx = getAudioContext();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/webaudio",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Web Audio helpers for Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -8,8 +8,7 @@
|
||||
"example": "examples"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"example": "npx parcel examples/repl.html",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
@@ -15,7 +15,6 @@ const hap2value = (hap) => {
|
||||
return { ...hap.value, velocity: hap.context.velocity };
|
||||
};
|
||||
|
||||
// TODO: bind logger
|
||||
export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps);
|
||||
export const webaudioOutput = (hap, deadline, hapDuration) => superdough(hap2value(hap), deadline, hapDuration);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/xen",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Xenharmonic API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
|
||||
Generated
+3
-3
@@ -430,6 +430,9 @@ importers:
|
||||
|
||||
packages/web:
|
||||
dependencies:
|
||||
'@rollup/plugin-replace':
|
||||
specifier: ^5.0.5
|
||||
version: 5.0.5
|
||||
'@strudel/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
@@ -3691,7 +3694,6 @@ packages:
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0
|
||||
magic-string: 0.30.5
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@3.1.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
|
||||
@@ -3717,7 +3719,6 @@ packages:
|
||||
'@types/estree': 1.0.0
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@rollup/rollup-android-arm-eabi@4.9.2:
|
||||
resolution: {integrity: sha512-RKzxFxBHq9ysZ83fn8Iduv3A283K7zPPYuhL/z9CQuyFrjwpErJx0h4aeb/bnJ+q29GRLgJpY66ceQ/Wcsn3wA==}
|
||||
@@ -7118,7 +7119,6 @@ packages:
|
||||
|
||||
/estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
dev: true
|
||||
|
||||
/estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
@@ -2001,6 +2001,27 @@ exports[`runs examples > example "dry" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "duration" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]",
|
||||
"[ 1/4 → 1/2 | note:a s:piano duration:0.5 ]",
|
||||
"[ 1/2 → 3/4 | note:f s:piano duration:0.5 ]",
|
||||
"[ 3/4 → 1/1 | note:e s:piano duration:0.5 ]",
|
||||
"[ 1/1 → 5/4 | note:c s:piano duration:1 ]",
|
||||
"[ 5/4 → 3/2 | note:a s:piano duration:1 ]",
|
||||
"[ 3/2 → 7/4 | note:f s:piano duration:1 ]",
|
||||
"[ 7/4 → 2/1 | note:e s:piano duration:1 ]",
|
||||
"[ 2/1 → 9/4 | note:c s:piano duration:2 ]",
|
||||
"[ 9/4 → 5/2 | note:a s:piano duration:2 ]",
|
||||
"[ 5/2 → 11/4 | note:f s:piano duration:2 ]",
|
||||
"[ 11/4 → 3/1 | note:e s:piano duration:2 ]",
|
||||
"[ 3/1 → 13/4 | note:c s:piano duration:0.5 ]",
|
||||
"[ 13/4 → 7/2 | note:a s:piano duration:0.5 ]",
|
||||
"[ 7/2 → 15/4 | note:f s:piano duration:0.5 ]",
|
||||
"[ 15/4 → 4/1 | note:e s:piano duration:0.5 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "early" example index 0 1`] = `
|
||||
[
|
||||
"[ -1/10 ⇜ (0/1 → 2/5) | s:hh ]",
|
||||
@@ -3521,48 +3542,6 @@ exports[`runs examples > example "layer" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "legato" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/32 | c4 ]",
|
||||
"[ 1/4 → 9/32 | eb4 ]",
|
||||
"[ 1/2 → 17/32 | g4 ]",
|
||||
"[ 3/4 → 25/32 | bb4 ]",
|
||||
"[ 1/1 → 17/16 | c4 ]",
|
||||
"[ 5/4 → 21/16 | eb4 ]",
|
||||
"[ 3/2 → 25/16 | g4 ]",
|
||||
"[ 7/4 → 29/16 | bb4 ]",
|
||||
"[ 2/1 → 17/8 | c4 ]",
|
||||
"[ 9/4 → 19/8 | eb4 ]",
|
||||
"[ 5/2 → 21/8 | g4 ]",
|
||||
"[ 11/4 → 23/8 | bb4 ]",
|
||||
"[ 3/1 → 51/16 | c4 ]",
|
||||
"[ 13/4 → 55/16 | eb4 ]",
|
||||
"[ 7/2 → 59/16 | g4 ]",
|
||||
"[ 15/4 → 63/16 | bb4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "legato" example index 0 2`] = `
|
||||
[
|
||||
"[ 0/1 → 1/16 | note:c3 ]",
|
||||
"[ 1/4 → 5/16 | note:eb3 ]",
|
||||
"[ 1/2 → 9/16 | note:g3 ]",
|
||||
"[ 3/4 → 13/16 | note:c4 ]",
|
||||
"[ 1/1 → 9/8 | note:c3 ]",
|
||||
"[ 5/4 → 11/8 | note:eb3 ]",
|
||||
"[ 3/2 → 13/8 | note:g3 ]",
|
||||
"[ 7/4 → 15/8 | note:c4 ]",
|
||||
"[ 2/1 → 9/4 | note:c3 ]",
|
||||
"[ 9/4 → 5/2 | note:eb3 ]",
|
||||
"[ 5/2 → 11/4 | note:g3 ]",
|
||||
"[ 11/4 → 3/1 | note:c4 ]",
|
||||
"[ 3/1 → 7/2 | note:c3 ]",
|
||||
"[ 13/4 → 15/4 | note:eb3 ]",
|
||||
"[ 7/2 → 4/1 | note:g3 ]",
|
||||
"[ 15/4 → 17/4 | note:c4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "leslie" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:0 ]",
|
||||
|
||||
+1
-4
@@ -7,7 +7,6 @@ import { evaluate } from '@strudel/transpiler';
|
||||
import { evalScope } from '@strudel/core';
|
||||
import * as strudel from '@strudel/core';
|
||||
import * as webaudio from '@strudel/webaudio';
|
||||
import controls from '@strudel/core/controls.mjs';
|
||||
// import gist from '@strudel/core/gist.js';
|
||||
import { mini, m } from '@strudel/mini/mini.mjs';
|
||||
// import * as voicingHelpers from '@strudel/tonal/voicings.mjs';
|
||||
@@ -21,7 +20,6 @@ import '@strudel/xen/xen.mjs';
|
||||
// import '@strudel/osc/osc.mjs';
|
||||
// import '@strudel/webaudio/webaudio.mjs';
|
||||
// import '@strudel/serial/serial.mjs';
|
||||
// import controls from '@strudel/core/controls.mjs';
|
||||
import '../website/src/repl/piano';
|
||||
|
||||
class MockedNode {
|
||||
@@ -153,10 +151,9 @@ evalScope(
|
||||
strudel,
|
||||
toneHelpersMocked,
|
||||
uiHelpersMocked,
|
||||
controls,
|
||||
webaudio,
|
||||
tonalHelpers,
|
||||
/* controls,
|
||||
/*
|
||||
toneHelpers,
|
||||
voicingHelpers,
|
||||
drawHelpers,
|
||||
|
||||
@@ -29,7 +29,7 @@ edit: the desktop app performance on linux is currently not that great.. the web
|
||||
The desktop App has the same features as the webapp, with the additional ability to load samples from disk. It is currently not documented yet, but you can do something like
|
||||
|
||||
```js
|
||||
await samples('~/music/xxx');
|
||||
samples('~/music/xxx');
|
||||
|
||||
s('my_sound');
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const examples = [
|
||||
`// "coastline" @by eddyflux
|
||||
await samples('github:eddyflux/crate')
|
||||
samples('github:eddyflux/crate')
|
||||
setcps(.75)
|
||||
let chords = chord("<Bbm9 Fm9>/4").dict('ireal')
|
||||
stack(
|
||||
@@ -30,7 +30,7 @@ stack(
|
||||
.late("[0 .01]*4").late("[0 .01]*2").size(4)`,
|
||||
`// "broken cut 1" @by froos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
samples('github:tidalcycles/dirt-samples')
|
||||
samples({
|
||||
'slap': 'https://cdn.freesound.org/previews/495/495416_10350281-lq.mp3',
|
||||
'whirl': 'https://cdn.freesound.org/previews/495/495313_10350281-lq.mp3',
|
||||
|
||||
@@ -51,7 +51,7 @@ Alternatively, you can get a taste of what Strudel can do by clicking play on th
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
stack(
|
||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8") // drums
|
||||
.speed(perlin.range(.7,.9)) // random sample speed variation
|
||||
|
||||
@@ -143,7 +143,7 @@ Because GitHub is a popular place for uploading open source samples, it has its
|
||||
bd: 'bd/BT0AADA.wav',
|
||||
sd: 'sd/rytm-01-classic.wav',
|
||||
hh: 'hh27/000_hh27closedhh.wav',
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
s("bd sd bd sd,hh*16")`}
|
||||
/>
|
||||
|
||||
@@ -174,7 +174,7 @@ It is also possible, to declare multiple files for one sound, using the array no
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
s("bd:0 bd:1,~ <sd:0 sd:1> ~ sd:0,[hh:0 hh:1]*4")`}
|
||||
/>
|
||||
|
||||
@@ -187,7 +187,7 @@ The sample number can also be set using `n`:
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
s("bd bd,~ sd ~ sd,hh*8").n("<0 1>")`}
|
||||
/>
|
||||
|
||||
@@ -231,7 +231,7 @@ For pitched sounds, you can use `note`, just like with synths:
|
||||
client:idle
|
||||
tune={`samples({
|
||||
'gtr': 'gtr/0001_cleanC.wav',
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').gain(.5)`}
|
||||
/>
|
||||
|
||||
@@ -242,7 +242,7 @@ If we want them to behave more like a synth, we can add `clip(1)`:
|
||||
client:idle
|
||||
tune={`samples({
|
||||
'gtr': 'gtr/0001_cleanC.wav',
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').clip(1)
|
||||
.gain(.5)`}
|
||||
/>
|
||||
@@ -256,7 +256,7 @@ If we have 2 samples with different base pitches, we can make them in tune by sp
|
||||
tune={`samples({
|
||||
'gtr': 'gtr/0001_cleanC.wav',
|
||||
'moog': { 'g3': 'moog/005_Mighty%20Moog%20G3.wav' },
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s("gtr,moog").clip(1)
|
||||
.gain(.5)`}
|
||||
/>
|
||||
@@ -272,7 +272,7 @@ We can also declare different samples for different regions of the keyboard:
|
||||
'g2': 'moog/004_Mighty%20Moog%20G2.wav',
|
||||
'g3': 'moog/005_Mighty%20Moog%20G3.wav',
|
||||
'g4': 'moog/006_Mighty%20Moog%20G4.wav',
|
||||
}}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}}, 'github:tidalcycles/dirt-samples');
|
||||
note("g2!2 <bb2 c3>!2, <c4@3 [<eb4 bb3> g4 f4]>")
|
||||
.s('moog').clip(1)
|
||||
.gain(.5).cpm(60)`}
|
||||
@@ -287,7 +287,7 @@ With it, you can enter any sample name(s) to query from [freesound.org](https://
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`await samples('shabda:bass:4,hihat:4,rimshot:2')
|
||||
tune={`samples('shabda:bass:4,hihat:4,rimshot:2')
|
||||
stack(
|
||||
n("0 1 2 3 0 1 2 3").s('bass'),
|
||||
n("0 1*2 2 3*2").s('hihat'),
|
||||
@@ -300,8 +300,8 @@ Note that the language code and the gender parameters are optional and default t
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`await samples('shabda/speech:the_drum,forever')
|
||||
await samples('shabda/speech/fr-FR/m:magnifique')
|
||||
tune={`samples('shabda/speech:the_drum,forever')
|
||||
samples('shabda/speech/fr-FR/m:magnifique')
|
||||
stack(
|
||||
s("the_drum*2").chop(16).speed(rand.range(0.85,1.1)),
|
||||
s("forever magnifique").slow(4).late(0.125)
|
||||
|
||||
@@ -34,11 +34,7 @@ Some of these have equivalent operators in the Mini Notation:
|
||||
|
||||
<JsDoc client:idle name="Pattern.late" h={0} />
|
||||
|
||||
## legato
|
||||
|
||||
<JsDoc client:idle name="Pattern.legato" h={0} />
|
||||
|
||||
## clip
|
||||
## clip / legato
|
||||
|
||||
<JsDoc client:idle name="clip" h={0} />
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ A sample can be looped and chopped like this:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
tune={`samples('github:yaxu/clean-breaks')
|
||||
s("amen/4").fit().chop(32)`}
|
||||
punchcard
|
||||
/>
|
||||
@@ -71,7 +71,7 @@ Let's add randmized doubling + reversing:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
tune={`samples('github:yaxu/clean-breaks')
|
||||
s("amen/4").fit().chop(16).cut(1)
|
||||
.sometimesBy(.5, ply("2"))
|
||||
.sometimesBy(.25, mul(speed("-1")))`}
|
||||
@@ -82,7 +82,7 @@ If we want to specify the order of samples, we can replace `chop` with `slice`:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
tune={`samples('github:yaxu/clean-breaks')
|
||||
s("amen/4").fit()
|
||||
.slice(8, "<0 1 2 3 4*2 5 6 [6 7]>*2")
|
||||
.cut(1).rarely(ply("2"))`}
|
||||
@@ -93,7 +93,7 @@ If we use `splice` instead of `slice`, the speed adjusts to the duration of the
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:yaxu/clean-breaks/main')
|
||||
tune={`samples('github:yaxu/clean-breaks')
|
||||
s("amen")
|
||||
.splice(8, "<0 1 2 3 4*2 5 6 [6 7]>*2")
|
||||
.cut(1).rarely(ply("2"))`}
|
||||
@@ -213,7 +213,7 @@ Using `run` with `n`, we can rush through a sample bank:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('bubo:fox')
|
||||
tune={`samples('bubo:fox')
|
||||
n(run(8)).s("ftabla")`}
|
||||
punchcard
|
||||
/>
|
||||
@@ -224,7 +224,7 @@ In this case, I hear the beginning at the third sample, which can be accounted f
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('bubo:fox')
|
||||
tune={`samples('bubo:fox')
|
||||
n(run(8)).s("ftabla").early(2/8)`}
|
||||
/>
|
||||
|
||||
@@ -232,7 +232,7 @@ Let's add some randomness:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('bubo:fox')
|
||||
tune={`samples('bubo:fox')
|
||||
n(run(8)).s("ftabla").early(2/8)
|
||||
.sometimes(mul(speed("1.5")))`}
|
||||
/>
|
||||
@@ -299,7 +299,7 @@ To simplify loading wavetables, any sample that starts with `wt_` will be looped
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||
tune={`samples('github:bubobubobubobubo/dough-waveforms')
|
||||
note("c eb g bb").s("wt_dbass").clip(2)`}
|
||||
/>
|
||||
|
||||
@@ -307,7 +307,7 @@ Running through different wavetables can also give interesting variations:
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||
tune={`samples('github:bubobubobubobubo/dough-waveforms')
|
||||
note("c2*8").s("wt_dbass").n(run(8))`}
|
||||
/>
|
||||
|
||||
@@ -315,7 +315,7 @@ note("c2*8").s("wt_dbass").n(run(8))`}
|
||||
|
||||
<MiniRepl
|
||||
client:visible
|
||||
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||
tune={`samples('github:bubobubobubobubo/dough-waveforms')
|
||||
note("c2*8").s("wt_dbass").n(run(8))
|
||||
.lpf(perlin.range(200,2000).slow(8))
|
||||
.lpenv(-3).lpa(.1).room(.5)`}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
|
||||
Note:
|
||||
|
||||
- this has been (partly) translated from https://tidalcycles.org/docs/patternlib/howtos/buildrhythms
|
||||
- this only sounds good with `samples('github:tidalcycles/Dirt-Samples/master')` in prebake
|
||||
- this only sounds good with `samples('github:tidalcycles/dirt-samples')` in prebake
|
||||
|
||||
# Build Rhythms
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ The content of a sequence will be squished into what's called a cycle.
|
||||
|
||||
cpm = cycles per minute
|
||||
|
||||
By default, the tempo is 60 cycles per minute = 1 cycle per second.
|
||||
By default, the tempo is 30 cycles per minute = 1 half cycle per second.
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -64,3 +64,8 @@
|
||||
#code .cm-focused {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#code .cm-matchingBracket {
|
||||
text-decoration: underline 0.18rem;
|
||||
text-underline-offset: 0.22rem;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
import { code2hash, getDrawContext, logger, silence } from '@strudel/core';
|
||||
import cx from '@src/cx.mjs';
|
||||
import { transpiler } from '@strudel/transpiler';
|
||||
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel/webaudio';
|
||||
import {
|
||||
getAudioContext,
|
||||
initAudioOnFirstClick,
|
||||
webaudioOutput,
|
||||
resetGlobalEffects,
|
||||
resetLoadedSounds,
|
||||
} from '@strudel/webaudio';
|
||||
import { defaultAudioDeviceName } from '../settings.mjs';
|
||||
import { getAudioDevices, setAudioDevice } from './util.mjs';
|
||||
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
||||
@@ -157,6 +163,7 @@ export function Repl({ embedded = false }) {
|
||||
};
|
||||
|
||||
const resetEditor = async () => {
|
||||
resetGlobalEffects();
|
||||
clearCanvas();
|
||||
resetLoadedSounds();
|
||||
editorRef.current.repl.setCps(0.5);
|
||||
@@ -183,6 +190,7 @@ export function Repl({ embedded = false }) {
|
||||
setViewingPatternData(patternData);
|
||||
clearCanvas();
|
||||
resetLoadedSounds();
|
||||
resetGlobalEffects();
|
||||
await prebake(); // declare default samples
|
||||
editorRef.current.setCode(code);
|
||||
editorRef.current.repl.evaluate(code);
|
||||
|
||||
@@ -77,6 +77,7 @@ export function SettingsTab({ started }) {
|
||||
const {
|
||||
theme,
|
||||
keybindings,
|
||||
isBracketMatchingEnabled,
|
||||
isLineNumbersDisplayed,
|
||||
isPatternHighlightingEnabled,
|
||||
isActiveLineHighlighted,
|
||||
@@ -137,6 +138,11 @@ export function SettingsTab({ started }) {
|
||||
></ButtonGroup>
|
||||
</FormItem>
|
||||
<FormItem label="Code Settings">
|
||||
<Checkbox
|
||||
label="Enable bracket matching"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isBracketMatchingEnabled', cbEvent.target.checked)}
|
||||
value={isBracketMatchingEnabled}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Display line numbers"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isLineNumbersDisplayed', cbEvent.target.checked)}
|
||||
|
||||
@@ -115,13 +115,12 @@ export async function prebake() {
|
||||
'numbers/8.wav',
|
||||
],
|
||||
},
|
||||
'github:tidalcycles/Dirt-Samples/master/',
|
||||
'github:tidalcycles/dirt-samples',
|
||||
{
|
||||
prebake: true,
|
||||
},
|
||||
),
|
||||
]);
|
||||
// await samples('github:tidalcycles/Dirt-Samples/master');
|
||||
}
|
||||
|
||||
const maxPan = noteToMidi('C8');
|
||||
|
||||
@@ -527,7 +527,7 @@ export const meltingsubmarine = `// "Melting submarine"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master/')
|
||||
samples('github:tidalcycles/dirt-samples')
|
||||
stack(
|
||||
s("bd:5,[~ <sd:1!3 sd:1(3,4,3)>],hh27(3,4,1)") // drums
|
||||
.speed(perlin.range(.7,.9)) // random sample speed variation
|
||||
@@ -574,7 +574,7 @@ samples({
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
perc: ['perc/002_perc2.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
|
||||
chord("<C^7 Am7 Dm7 G7>*2").dict('lefthand').anchor("G4").voicing()
|
||||
.stack(n("0@6 [<1 2> <2 0> 1]@2").scale('C5 major'))
|
||||
@@ -608,7 +608,7 @@ samples({
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
}, 'github:tidalcycles/dirt-samples');
|
||||
|
||||
note("<8(3,8) <7 7*2> [4 5@3] 8>".sub(1) // sub 1 -> 1-indexed
|
||||
.layer(
|
||||
@@ -797,7 +797,7 @@ export const amensister = `// "Amensister"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
samples('github:tidalcycles/dirt-samples')
|
||||
|
||||
stack(
|
||||
// amen
|
||||
@@ -906,7 +906,7 @@ export const arpoon = `// "Arpoon"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
samples('github:tidalcycles/dirt-samples')
|
||||
|
||||
n("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2)
|
||||
.offset("<<1 2> 2 1 1>")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { controls, evalScope, hash2code, logger } from '@strudel/core';
|
||||
import { evalScope, hash2code, logger } from '@strudel/core';
|
||||
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
|
||||
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel/webaudio';
|
||||
|
||||
@@ -92,11 +92,7 @@ export function loadModules() {
|
||||
modules = modules.concat([import('@strudel/midi'), import('@strudel/osc')]);
|
||||
}
|
||||
|
||||
return evalScope(
|
||||
controls, // sadly, this cannot be exported from core direclty
|
||||
settingPatterns,
|
||||
...modules,
|
||||
);
|
||||
return evalScope(settingPatterns, ...modules);
|
||||
}
|
||||
|
||||
let lastShared;
|
||||
|
||||
@@ -7,6 +7,7 @@ export const defaultAudioDeviceName = 'System Standard';
|
||||
export const defaultSettings = {
|
||||
activeFooter: 'intro',
|
||||
keybindings: 'codemirror',
|
||||
isBracketMatchingEnabled: true,
|
||||
isLineNumbersDisplayed: true,
|
||||
isActiveLineHighlighted: true,
|
||||
isAutoCompletionEnabled: false,
|
||||
@@ -40,6 +41,7 @@ export function useSettings() {
|
||||
return {
|
||||
...state,
|
||||
isZen: [true, 'true'].includes(state.isZen) ? true : false,
|
||||
isBracketMatchingEnabled: [true, 'true'].includes(state.isBracketMatchingEnabled) ? true : false,
|
||||
isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false,
|
||||
isActiveLineHighlighted: [true, 'true'].includes(state.isActiveLineHighlighted) ? true : false,
|
||||
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
|
||||
|
||||
Reference in New Issue
Block a user