Compare commits

..

3 Commits

Author SHA1 Message Date
Felix Roos c21bc161d6 some osc helper functions 2024-02-18 20:41:06 +01:00
Felix Roos 8d65092c97 update osc readme 2024-02-15 10:00:01 +01:00
Felix Roos b6a9943b5a use es6 import 2024-02-15 09:59:52 +01:00
60 changed files with 1659 additions and 1782 deletions
@@ -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'),
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
});
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev)`));
+2 -3
View File
@@ -16,7 +16,6 @@
</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';
@@ -53,8 +52,8 @@
});
function getTune() {
return `samples('github:tidalcycles/dirt-samples')
setcps(1);
return `await samples('github:tidalcycles/Dirt-Samples/master')
stack(
// amen
n("0 1 2 3 4 5 6 7")
@@ -1,4 +1,4 @@
<script src="https://unpkg.com/@strudel/repl@1.0.2"></script>
<script src="https://unpkg.com/@strudel/repl@0.9.4"></script>
<strudel-editor>
<!--
// @date 23-08-15
-8
View File
@@ -1,8 +0,0 @@
<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>
+2 -1
View File
@@ -1,6 +1,6 @@
import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes';
import { drawPianoroll, evalScope } from '@strudel/core';
import { drawPianoroll, evalScope, controls } from '@strudel/core';
import './style.css';
import { initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
@@ -25,6 +25,7 @@ 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'),
+6 -6
View File
@@ -1,6 +1,6 @@
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
await samples('github:felixroos/samples/main')
await 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 @@ samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-dru
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
await samples('github:felixroos/samples/main')
await 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
samples('github:felixroos/samples')
samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
await samples('github:felixroos/samples/main')
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
setcps(.5)
+1 -1
View File
@@ -15,7 +15,7 @@
<script type="module">
import { initStrudel } from '@strudel/web';
initStrudel({
prebake: () => samples('github:tidalcycles/dirt-samples'),
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
});
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
+9 -4
View File
@@ -1,5 +1,5 @@
import { repl, evalScope } from '@strudel/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick, registerSynthSounds } from '@strudel/webaudio';
import { controls, repl, evalScope } from '@strudel/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
import tune from './tune.mjs';
@@ -7,9 +7,14 @@ const ctx = getAudioContext();
const input = document.getElementById('text');
input.innerHTML = tune;
initAudioOnFirstClick();
registerSynthSounds();
evalScope(import('@strudel/core'), import('@strudel/mini'), import('@strudel/webaudio'), import('@strudel/tonal'));
evalScope(
controls,
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/webaudio'),
import('@strudel/tonal'),
);
const { evaluate } = repl({
defaultOutput: webaudioOutput,
+2 -2
View File
@@ -1,5 +1,5 @@
export default `samples('github:tidalcycles/dirt-samples')
setcps(1)
export default `await samples('github:tidalcycles/Dirt-Samples/master')
stack(
// amen
n("0 1 2 3 4 5 6 7")
+1 -1
View File
@@ -12,7 +12,7 @@
const init = Promise.all([
initAudioOnFirstClick(),
samples('github:tidalcycles/dirt-samples'),
samples('github:tidalcycles/Dirt-Samples/master'),
registerSynthSounds(),
]);
+1 -6
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, bracketMatching } from '@codemirror/language';
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { Compartment, EditorState, Prec } from '@codemirror/state';
import {
EditorView,
@@ -24,7 +24,6 @@ import { persistentAtom } from '@nanostores/persistent';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []),
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
theme,
isAutoCompletionEnabled,
@@ -38,7 +37,6 @@ const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [ke
export const defaultSettings = {
keybindings: 'codemirror',
isBracketMatchingEnabled: false,
isLineNumbersDisplayed: true,
isActiveLineHighlighted: false,
isAutoCompletionEnabled: false,
@@ -292,9 +290,6 @@ export class StrudelMirror {
setLineWrappingEnabled(enabled) {
this.reconfigureExtension('isLineWrappingEnabled', enabled);
}
setBracketMatchingEnabled(enabled) {
this.reconfigureExtension('isBracketMatchingEnabled', enabled);
}
setLineNumbersDisplayed(enabled) {
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
}
+3 -1
View File
@@ -1,4 +1,6 @@
import { Pattern, getDrawContext, silence, register, pure, createParams } from './index.mjs';
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;
let clearColor = '#22222210';
+1420 -1445
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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.endClipped >= phase - lookbehind - lookahead)
.filter((h) => h.whole?.end >= phase - lookbehind - lookahead)
// add new haps with onset (think right edge bars scrolling in)
.concat(haps.filter((h) => h.hasOnset()));
const time = phase - lookahead;
+1 -7
View File
@@ -41,17 +41,11 @@ 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]);
const p = flatten(result[1][0]).concat(flatten(result[1][1]));
if (inverted) {
return p.map((x) => (x === 0 ? 1 : 0));
}
return p;
return flatten(result[1][0]).concat(flatten(result[1][1]));
};
/**
+1 -11
View File
@@ -3,7 +3,6 @@ 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 {
/*
@@ -33,16 +32,7 @@ export class Hap {
}
get duration() {
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;
return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1);
}
get endClipped() {
+1 -2
View File
@@ -4,12 +4,11 @@ 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 * as controls from './controls.mjs'; // legacy
import controls from './controls.mjs';
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';
-10
View File
@@ -1,16 +1,6 @@
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(
+24 -3
View File
@@ -2203,6 +2203,12 @@ 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})`);
});
@@ -2235,6 +2241,21 @@ 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
@@ -2301,10 +2322,10 @@ const _loopAt = function (factor, pat, cps = 0.5) {
* @memberof Pattern
* @returns Pattern
* @example
* samples('github:tidalcycles/dirt-samples')
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(0.75)
* @example
* samples('github:tidalcycles/dirt-samples')
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks125").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
*/
@@ -2330,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
* samples('github:tidalcycles/dirt-samples')
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165")
* .splice(8, "0 1 [2 3 0]@2 3 0@2 7")
*/
+4 -65
View File
@@ -244,68 +244,9 @@ 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}
@@ -316,23 +257,21 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
* @example
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
*/
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
export const inhabit = register('inhabit', function (lookup, pat) {
return _pick(lookup, pat, true).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, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, true).squeezeJoin();
export const inhabitmod = register('inhabit', function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
});
/**
+5 -5
View File
@@ -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 { s } from '../controls.mjs';
import controls from '../controls.mjs';
import { mini } from '../../mini/mini.mjs';
import { describe, it, expect } from 'vitest';
describe('controls', () => {
it('should support controls', () => {
expect(s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
expect(controls.s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
});
it('should support compound controls', () => {
expect(s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
expect(s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
expect(controls.s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
expect(controls.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(s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
expect(controls.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 },
]);
+2 -1
View File
@@ -51,8 +51,9 @@ import {
import { steady } from '../signal.mjs';
import { n, s } from '../controls.mjs';
import controls 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);
+2 -1
View File
@@ -6,7 +6,8 @@ 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 { n } from '../controls.mjs';
import controls from '../controls.mjs';
const { n } = controls;
describe('Value', () => {
it('unionWith', () => {
+2 -2
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, cps) => {
return this.onTrigger((time, hap, currentTime) => {
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() / cps) * 1000 - 10);
const duration = Math.floor(hap.duration.valueOf() * 1000 - 10);
const roundedOffset = Math.round(offset);
const midichan = (hap.value.midichan ?? 1) - 1;
const requestedport = output ?? 'IAC';
+3 -9
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() / cps) * 1000 - 10);
const duration = Math.floor(hap.duration.valueOf() * 1000 - 10);
if (note != null) {
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
const midiNote = new Note(midiNumber, { attack: velocity, duration });
@@ -167,16 +167,10 @@ 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 || WebMidi.enabled) {
if (initial) {
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
logger(
`Midi enabled! Using "${device.name}". ${
+3 -11
View File
@@ -4,18 +4,10 @@ OSC output for strudel patterns! Currently only tested with super collider / sup
## Usage
OSC will only work if you run the REPL locally + the OSC server besides it:
From the project root:
Make sure you've [set up strudel locally](https://github.com/tidalcycles/strudel/blob/main/CONTRIBUTING.md#project-setup). Then run:
```js
npm run repl
```
and in a seperate shell:
```js
npm run osc
pnpm osc
```
This should give you
@@ -26,7 +18,7 @@ osc server running on port 57121
websocket server running on port 8080
```
Now open Supercollider (with the super dirt startup file)
Now open Supercollider (with the super dirt startup file) or run `sclang`.
Now open the REPL and type:
+26
View File
@@ -8,6 +8,7 @@ import OSC from 'osc-js';
import { logger, parseNumeral, Pattern } from '@strudel/core';
let subs = {};
let connection; // Promise<OSC>
function connect() {
if (!connection) {
@@ -25,6 +26,13 @@ function connect() {
console.log('[osc] disconnected');
reject('OSC connection closed');
});
osc.on('*', (msg) => {
Object.entries(subs).forEach(([route, callback]) => {
if (msg.address.startsWith(route)) {
callback(msg.args, msg);
}
});
});
osc.on('error', (err) => reject(err));
}).catch((err) => {
connection = undefined;
@@ -34,6 +42,24 @@ function connect() {
return connection;
}
export const connectOSC = connect;
export async function sendOSC(...msg) {
const osc = await connect();
const t = Date.now();
const message = new OSC.Message(...msg);
const bundle = new OSC.Bundle([message], t);
bundle.timestamp(t); // workaround for https://github.com/adzialocha/osc-js/issues/60
osc.send(bundle);
}
export function subOSC(route, callback) {
subs[route] = callback;
}
export function unsubOSC(route) {
delete subs[route];
}
/**
*
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
+1 -1
View File
@@ -4,7 +4,7 @@ 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/>.
*/
const OSC = require('osc-js');
import OSC from 'osc-js';
const config = {
receiver: 'ws', // @param {string} Where messages sent via 'send' method will be delivered to, 'ws' for Websocket clients, 'udp' for udp client
+3 -4
View File
@@ -1,11 +1,10 @@
{
"name": "@strudel/repl",
"version": "1.0.2",
"version": "1.0.1",
"description": "Strudel REPL as a Web Component",
"module": "index.mjs",
"main": "index.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
+2 -1
View File
@@ -1,4 +1,4 @@
import { noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio';
import * as core from '@strudel/core';
@@ -17,6 +17,7 @@ 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/';
+2 -2
View File
@@ -19,7 +19,7 @@ import { superdough, samples, initAudioOnFirstClick, registerSynthSounds } from
const init = Promise.all([
initAudioOnFirstClick(),
samples('github:tidalcycles/dirt-samples'),
samples('github:tidalcycles/Dirt-Samples/master'),
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')
samples('github:tidalcycles/Dirt-Samples/master')
```
The format is `github:<user>/<repo>/<branch>`.
+16 -32
View File
@@ -99,27 +99,6 @@ 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') {
@@ -129,19 +108,15 @@ export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '')
throw new Error('wrong sample map format for ' + key);
}
baseUrl = value._base || baseUrl;
baseUrl = resolveSpecialPaths(baseUrl);
if (baseUrl.startsWith('github:')) {
baseUrl = githubPath(baseUrl, '');
}
const fullUrl = (v) => baseUrl + v;
const replaceUrl = (v) => (baseUrl + v).replace('github:', 'https://raw.githubusercontent.com/');
if (Array.isArray(value)) {
//return [key, value.map(replaceUrl)];
value = value.map(fullUrl);
value = value.map(replaceUrl);
} else {
// must be object
value = Object.fromEntries(
Object.entries(value).map(([note, samples]) => {
return [note, (typeof samples === 'string' ? [samples] : samples).map(fullUrl)];
return [note, (typeof samples === 'string' ? [samples] : samples).map(replaceUrl)];
}),
);
}
@@ -167,7 +142,7 @@ function getSamplesPrefixHandler(url) {
/**
* Loads a collection of samples to use with `s`
* @example
* samples('github:tidalcycles/dirt-samples');
* samples('github:tidalcycles/Dirt-Samples/master');
* s("[bd ~]*2, [~ hh]*2, ~ sd")
* @example
* samples({
@@ -190,9 +165,18 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
if (handler) {
return handler(sampleMap);
}
sampleMap = resolveSpecialPaths(sampleMap);
if (sampleMap.startsWith('bubo:')) {
const [_, repo] = sampleMap.split(':');
sampleMap = `github:Bubobubobubobubo/dough-${repo}`;
}
if (sampleMap.startsWith('github:')) {
sampleMap = githubPath(sampleMap, 'strudel.json');
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`;
}
if (sampleMap.startsWith('shabda:')) {
let [_, path] = sampleMap.split('shabda:');
@@ -251,7 +235,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
nudge = 0, // TODO: is this in seconds?
cut,
loop,
clip = undefined, // if set, samples will be cut off when the hap ends
clip = undefined, // if 1, samples will be cut off when the hap ends
n = 0,
note,
speed = 1, // sample playback speed
-6
View File
@@ -253,11 +253,6 @@ 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') {
@@ -518,7 +513,6 @@ 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);
+2 -1
View File
@@ -7,9 +7,10 @@ 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, n, seq } from '@strudel/core';
import { pure, controls, 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 ', () => {
-7
View File
@@ -103,8 +103,6 @@ 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)));
@@ -135,11 +133,6 @@ 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');
}
@@ -23,9 +23,6 @@ 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');", {
+3 -16
View File
@@ -1,7 +1,8 @@
import { getLeafLocations } from '@strudel/mini';
import { parse } from 'acorn';
import escodegen from 'escodegen';
import { parse } from 'acorn';
import { walk } from 'estree-walker';
import { isNoteWithOctave } from '@strudel/core';
import { getLeafLocations } from '@strudel/mini';
export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
@@ -46,9 +47,6 @@ export function transpiler(input, options = {}) {
});
return this.replace(widgetWithLocation(node));
}
if (isBareSamplesCall(node, parent)) {
return this.replace(withAwait(node));
}
},
leave(node, parent, prop, index) {},
});
@@ -121,14 +119,3 @@ 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,
};
}
+1 -1
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'),
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
});
document.getElementById('play').addEventListener('click',
+4 -6
View File
@@ -1,11 +1,10 @@
{
"name": "@strudel/web",
"version": "1.0.3",
"version": "1.0.1",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"module": "web.mjs",
"main": "web.mjs",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
@@ -37,8 +36,7 @@
"@strudel/mini": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*",
"@rollup/plugin-replace": "^5.0.5"
"@strudel/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^5.0.10"
+3 -11
View File
@@ -1,7 +1,6 @@
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({
@@ -9,18 +8,11 @@ export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'web.mjs'),
name: 'strudel',
formats: ['es', 'iife'],
fileName: (ext) => ({ es: 'index.mjs', iife: 'index.js' })[ext],
formats: ['es'],
fileName: (ext) => ({ es: 'index.mjs' })[ext],
},
rollupOptions: {
// external: [...Object.keys(dependencies)],
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
preventAssignment: true,
}),
],
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
+2 -1
View File
@@ -5,7 +5,7 @@ export * from '@strudel/transpiler';
export * from '@strudel/mini';
export * from '@strudel/tonal';
export * from '@strudel/webaudio';
import { Pattern, evalScope } from '@strudel/core';
import { Pattern, evalScope, controls } from '@strudel/core';
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel/webaudio';
// import { registerSoundfonts } from '@strudel/soundfonts';
import { evaluate as _evaluate } from '@strudel/transpiler';
@@ -15,6 +15,7 @@ import { miniAllStrings } from '@strudel/mini';
export async function defaultPrebake() {
const loadModules = evalScope(
evalScope,
controls,
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/tonal'),
+2 -1
View File
@@ -12,8 +12,9 @@ npm i @strudel/webaudio --save
## Example
```js
import { repl, note } from "@strudel/core";
import { repl, controls } from "@strudel/core";
import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel/webaudio";
const { note } = controls;
initAudioOnFirstClick();
const ctx = getAudioContext();
+1
View File
@@ -15,6 +15,7 @@ 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);
+3 -3
View File
@@ -430,9 +430,6 @@ importers:
packages/web:
dependencies:
'@rollup/plugin-replace':
specifier: ^5.0.5
version: 5.0.5
'@strudel/core':
specifier: workspace:*
version: link:../core
@@ -3694,6 +3691,7 @@ 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==}
@@ -3719,6 +3717,7 @@ 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==}
@@ -7119,6 +7118,7 @@ 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==}
+42 -21
View File
@@ -2001,27 +2001,6 @@ 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 ]",
@@ -3542,6 +3521,48 @@ 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 ]",
+4 -1
View File
@@ -7,6 +7,7 @@ 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';
@@ -20,6 +21,7 @@ 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 {
@@ -151,9 +153,10 @@ 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
samples('~/music/xxx');
await samples('~/music/xxx');
s('my_sound');
```
+2 -2
View File
@@ -1,6 +1,6 @@
export const examples = [
`// "coastline" @by eddyflux
samples('github:eddyflux/crate')
await 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
samples('github:tidalcycles/dirt-samples')
await samples('github:tidalcycles/Dirt-Samples/master')
samples({
'slap': 'https://cdn.freesound.org/previews/495/495416_10350281-lq.mp3',
'whirl': 'https://cdn.freesound.org/previews/495/495313_10350281-lq.mp3',
+1 -1
View File
@@ -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');
}, 'github:tidalcycles/Dirt-Samples/master/');
stack(
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8") // drums
.speed(perlin.range(.7,.9)) // random sample speed variation
+10 -10
View File
@@ -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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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');
}}, 'github:tidalcycles/Dirt-Samples/master/');
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={`samples('shabda:bass:4,hihat:4,rimshot:2')
tune={`await 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={`samples('shabda/speech:the_drum,forever')
samples('shabda/speech/fr-FR/m:magnifique')
tune={`await samples('shabda/speech:the_drum,forever')
await 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)
+5 -1
View File
@@ -34,7 +34,11 @@ Some of these have equivalent operators in the Mini Notation:
<JsDoc client:idle name="Pattern.late" h={0} />
## clip / legato
## legato
<JsDoc client:idle name="Pattern.legato" h={0} />
## clip
<JsDoc client:idle name="clip" h={0} />
+10 -10
View File
@@ -60,7 +60,7 @@ A sample can be looped and chopped like this:
<MiniRepl
client:visible
tune={`samples('github:yaxu/clean-breaks')
tune={`await samples('github:yaxu/clean-breaks/main')
s("amen/4").fit().chop(32)`}
punchcard
/>
@@ -71,7 +71,7 @@ Let's add randmized doubling + reversing:
<MiniRepl
client:visible
tune={`samples('github:yaxu/clean-breaks')
tune={`await samples('github:yaxu/clean-breaks/main')
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={`samples('github:yaxu/clean-breaks')
tune={`await samples('github:yaxu/clean-breaks/main')
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={`samples('github:yaxu/clean-breaks')
tune={`await samples('github:yaxu/clean-breaks/main')
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={`samples('bubo:fox')
tune={`await 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={`samples('bubo:fox')
tune={`await samples('bubo:fox')
n(run(8)).s("ftabla").early(2/8)`}
/>
@@ -232,7 +232,7 @@ Let's add some randomness:
<MiniRepl
client:visible
tune={`samples('bubo:fox')
tune={`await 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={`samples('github:bubobubobubobubo/dough-waveforms')
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
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={`samples('github:bubobubobubobubo/dough-waveforms')
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
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={`samples('github:bubobubobubobubo/dough-waveforms')
tune={`await samples('github:bubobubobubobubo/dough-waveforms/main')
note("c2*8").s("wt_dbass").n(run(8))
.lpf(perlin.range(200,2000).slow(8))
.lpenv(-3).lpa(.1).room(.5)`}
+1 -1
View File
@@ -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')` in prebake
- this only sounds good with `samples('github:tidalcycles/Dirt-Samples/master')` in prebake
# Build Rhythms
-5
View File
@@ -64,8 +64,3 @@
#code .cm-focused {
outline: none;
}
#code .cm-matchingBracket {
text-decoration: underline 0.18rem;
text-underline-offset: 0.22rem;
}
+1 -9
View File
@@ -7,13 +7,7 @@ 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,
resetGlobalEffects,
resetLoadedSounds,
} from '@strudel/webaudio';
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel/webaudio';
import { defaultAudioDeviceName } from '../settings.mjs';
import { getAudioDevices, setAudioDevice } from './util.mjs';
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
@@ -163,7 +157,6 @@ export function Repl({ embedded = false }) {
};
const resetEditor = async () => {
resetGlobalEffects();
clearCanvas();
resetLoadedSounds();
editorRef.current.repl.setCps(0.5);
@@ -190,7 +183,6 @@ export function Repl({ embedded = false }) {
setViewingPatternData(patternData);
clearCanvas();
resetLoadedSounds();
resetGlobalEffects();
await prebake(); // declare default samples
editorRef.current.setCode(code);
editorRef.current.repl.evaluate(code);
-6
View File
@@ -77,7 +77,6 @@ export function SettingsTab({ started }) {
const {
theme,
keybindings,
isBracketMatchingEnabled,
isLineNumbersDisplayed,
isPatternHighlightingEnabled,
isActiveLineHighlighted,
@@ -138,11 +137,6 @@ 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)}
+2 -1
View File
@@ -115,12 +115,13 @@ export async function prebake() {
'numbers/8.wav',
],
},
'github:tidalcycles/dirt-samples',
'github:tidalcycles/Dirt-Samples/master/',
{
prebake: true,
},
),
]);
// await samples('github:tidalcycles/Dirt-Samples/master');
}
const maxPan = noteToMidi('C8');
+5 -5
View File
@@ -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
samples('github:tidalcycles/dirt-samples')
await samples('github:tidalcycles/Dirt-Samples/master/')
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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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');
}, 'github:tidalcycles/Dirt-Samples/master/');
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
samples('github:tidalcycles/dirt-samples')
await samples('github:tidalcycles/Dirt-Samples/master')
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
samples('github:tidalcycles/dirt-samples')
await samples('github:tidalcycles/Dirt-Samples/master')
n("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2)
.offset("<<1 2> 2 1 1>")
+6 -2
View File
@@ -1,4 +1,4 @@
import { evalScope, hash2code, logger } from '@strudel/core';
import { controls, evalScope, hash2code, logger } from '@strudel/core';
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel/webaudio';
@@ -92,7 +92,11 @@ export function loadModules() {
modules = modules.concat([import('@strudel/midi'), import('@strudel/osc')]);
}
return evalScope(settingPatterns, ...modules);
return evalScope(
controls, // sadly, this cannot be exported from core direclty
settingPatterns,
...modules,
);
}
let lastShared;
-2
View File
@@ -7,7 +7,6 @@ export const defaultAudioDeviceName = 'System Standard';
export const defaultSettings = {
activeFooter: 'intro',
keybindings: 'codemirror',
isBracketMatchingEnabled: true,
isLineNumbersDisplayed: true,
isActiveLineHighlighted: true,
isAutoCompletionEnabled: false,
@@ -41,7 +40,6 @@ 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,