Compare commits

..

65 Commits

Author SHA1 Message Date
Felix Roos 3e988d1125 support n / note in getPlayableNoteValue 2022-04-22 13:48:53 +02:00
alex 1029f8674c fix the fix for echo 2022-04-22 12:35:43 +01:00
alex c801660b2a use rational multiplication in echo 2022-04-22 12:30:16 +01:00
alex 48d4541ed7 build 2022-04-22 12:12:11 +01:00
Alex McLean 8007ea6a7e Merge pull request #83 from tidalcycles/pattern-composers
Add pattern composers, implements #82
2022-04-22 12:08:11 +01:00
alex 8e441ecb19 fix striate: union -> set 2022-04-22 12:07:23 +01:00
alex c305e82f5c Merge branch 'main' into pattern-composers 2022-04-22 11:52:36 +01:00
alex 87cfa30508 Extra test for ply (and prettify) 2022-04-22 11:51:41 +01:00
alex 2c224d5092 Make comparisons rational in _compress 2022-04-22 11:46:34 +01:00
Felix Roos 72eeaf446e fix: cycleArg imprecise fraction operation 2022-04-22 12:40:23 +02:00
Felix Roos 51a806b0ba Merge branch 'main' of https://github.com/tidalcycles/strudel 2022-04-22 11:49:42 +02:00
Felix Roos 52840f9141 add tune + fix another 2022-04-22 11:48:57 +02:00
alex 974503160d squeezeflip, tests for add including failing one.. 2022-04-22 09:51:44 +01:00
alex d9e3ff3256 Tests for set, setFlip and setSqueeze 2022-04-22 09:27:23 +01:00
alex b510ab0371 Make set work for primative patterns, allow ops accept sequence 2022-04-22 09:26:39 +01:00
Felix Roos 5eba78d795 Merge pull request #84 from mindofmatthew/tidying
Tidy up a couple of old files
2022-04-21 20:57:45 +02:00
Matthew Kaney 884c13a22d Remove old files 2022-04-21 13:14:37 -04:00
Alex McLean 90c5605409 Merge pull request #76 from tidalcycles/more-functions
add `striate()`
2022-04-21 14:21:29 +01:00
alex 3ae6443c59 Tidy test and console logs 2022-04-21 14:17:28 +01:00
alex b370c405da Add pattern composers, implements #82 2022-04-21 14:13:26 +01:00
Felix Roos 36d1a68b9a build 2022-04-20 20:22:50 +02:00
Felix Roos 58bbbecb2d package-lock 2022-04-20 20:19:56 +02:00
Felix Roos 1cdff32b5c Merge pull request #77 from tidalcycles/webaudio-compat
Webaudio in REPL
2022-04-20 20:17:26 +02:00
Felix Roos 7dbdb005b0 fix adsr + add example tune 2022-04-20 20:15:36 +02:00
Felix Roos e6c15dc3c5 add waa example 2022-04-19 21:46:44 +02:00
Felix Roos 1778d19bde Merge remote-tracking branch 'origin/main' into webaudio-compat 2022-04-19 21:35:08 +02:00
alex 56c3c0462c build 2022-04-19 18:16:00 +01:00
Alex McLean 80102353d6 Merge pull request #80 from tidalcycles/webserial
Basic webserial support
2022-04-19 14:31:34 +01:00
alex d7dc158861 Tidy up 2022-04-19 14:28:16 +01:00
alex b52c42c31d First go at serial output 2022-04-19 13:40:01 +01:00
Felix Roos f8739e341b respect midi numbers 2022-04-18 18:57:28 +02:00
Felix Roos 1ead12a2a3 patternify filter 2022-04-18 18:57:02 +02:00
Felix Roos c2feb6e214 remove logs 2022-04-18 18:45:18 +02:00
Felix Roos 3d74b8a2fa Merge commit '83a46c68dc6583ad36b256bc0d03e0913ecbc1f0' into webaudio-compat 2022-04-18 18:33:47 +02:00
Felix Roos 83a46c68dc webaudio@0.0.6 2022-04-18 15:03:11 +02:00
Felix Roos bcefce9df7 package-lock 2022-04-18 14:54:19 +02:00
Felix Roos dcb5af3339 fix: core imports in scheduler 2022-04-18 14:51:47 +02:00
alex 1d56d79757 add striate() 2022-04-17 23:18:01 +01:00
alex 0b370bd40d Make intersection_e actually raise an exception 2022-04-17 18:38:34 +01:00
alex 07a5ae83b5 build 2022-04-17 18:31:20 +01:00
Alex McLean cc60addbaa Merge pull request #75 from tidalcycles/more-tests
Try to fix appLeft / appRight
2022-04-17 18:29:48 +01:00
alex 30b7429169 Fix appLeft/appRight to discard nonmatching events 2022-04-17 18:28:55 +01:00
alex 28ebf6559e Fix steady() 2022-04-17 18:25:38 +01:00
alex 1c6e3dd9a6 A couple of tests for continuous haps/patterns 2022-04-17 18:23:34 +01:00
Felix Roos 6bba661e24 experiment with passing time to withAudioNode 2022-04-17 12:01:13 +02:00
alex eccb20c3b7 Fix choose and add weighted wchoose 2022-04-16 23:23:15 +01:00
Felix Roos f210c4425b make webaudio compatible with repl
+ rename osc to wave
+ migrate repl.html
2022-04-16 21:39:14 +02:00
Felix Roos e0e3ab941e remove experimental tone tutorial 2022-04-16 21:38:23 +02:00
Felix Roos 9cc568fe3e remove tonejs experimental features 2022-04-16 21:36:10 +02:00
Felix Roos a638f9505f test fromMidi + getFrequency 2022-04-16 21:34:19 +02:00
Felix Roos 1b35dd0c55 add preliminary getFrequency 2022-04-16 21:34:05 +02:00
alex d394b4939f build 2022-04-16 18:34:11 +01:00
alex c751d4eb79 Make sure random numbers are positive 2022-04-16 18:32:17 +01:00
alex bb147abb7f Merge branch 'main' of github.com:tidalcycles/strudel 2022-04-16 15:06:02 +01:00
alex 1c3793e2ef Add someCycles and someCyclesBy 2022-04-16 15:05:56 +01:00
alex 1274641f89 Tweak applicative 2022-04-16 15:05:37 +01:00
Felix Roos 803b50535f add repl link 2022-04-16 15:49:40 +02:00
Felix Roos 028af735c9 remove mini for now 2022-04-16 15:46:48 +02:00
Felix Roos d42cd6efaf Publish
- @strudel.cycles/mini@0.0.7
 - @strudel.cycles/webaudio@0.0.5
2022-04-16 15:37:05 +02:00
Felix Roos 2cf129408b use skypack in example 2022-04-16 15:36:49 +02:00
Felix Roos 0ed1c75c38 fix core dependency 2022-04-16 15:35:01 +02:00
Felix Roos 5588b62b40 Publish
- @strudel.cycles/webaudio@0.0.4
2022-04-16 15:24:40 +02:00
Felix Roos 03cb80b75b add core dependeny 2022-04-16 15:24:24 +02:00
Felix Roos 86a3fd6e22 Publish
- @strudel.cycles/webaudio@0.0.3
2022-04-16 15:17:30 +02:00
Felix Roos 27ebe7ba73 stub change 2022-04-16 15:17:02 +02:00
35 changed files with 574 additions and 3968 deletions
+3 -3
View File
@@ -1,16 +1,16 @@
{
"files": {
"main.css": "/static/css/main.0d689283.css",
"main.js": "/static/js/main.f7b8088f.js",
"main.js": "/static/js/main.7e790d7f.js",
"static/js/787.1c52cb78.chunk.js": "/static/js/787.1c52cb78.chunk.js",
"static/media/logo.svg": "/static/media/logo.ac95051720b3dccfe511e0e02d8e1029.svg",
"index.html": "/index.html",
"main.0d689283.css.map": "/static/css/main.0d689283.css.map",
"main.f7b8088f.js.map": "/static/js/main.f7b8088f.js.map",
"main.7e790d7f.js.map": "/static/js/main.7e790d7f.js.map",
"787.1c52cb78.chunk.js.map": "/static/js/787.1c52cb78.chunk.js.map"
},
"entrypoints": [
"static/css/main.0d689283.css",
"static/js/main.f7b8088f.js"
"static/js/main.7e790d7f.js"
]
}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Strudel REPL"/><title>Strudel REPL</title><script defer="defer" src="/static/js/main.f7b8088f.js"></script><link href="/static/css/main.0d689283.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Strudel REPL"/><title>Strudel REPL</title><script defer="defer" src="/static/js/main.7e790d7f.js"></script><link href="/static/css/main.0d689283.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico"><link rel="stylesheet" type="text/css" href="/tutorial/index.757e9f9d.css"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Strudel REPL"><title>Strudel Tutorial</title></head><body> <div id="root"></div> <noscript>You need to enable JavaScript to run this app.</noscript> <script src="/tutorial/index.36c08014.js" defer></script> </body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico"><link rel="stylesheet" type="text/css" href="/tutorial/index.757e9f9d.css"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="Strudel REPL"><title>Strudel Tutorial</title></head><body> <div id="root"></div> <noscript>You need to enable JavaScript to run this app.</noscript> <script src="/tutorial/index.a0cd6c24.js" defer></script> </body></html>
-14
View File
@@ -1,14 +0,0 @@
<!DOCTYPE html>
<script>import("./strudel.js").then(m => strudel = m)
</script>
<html>
<head>
<title>Bingo</title>
</head>
<body>
<button onclick="foo()">hello</button>
<button onclick="bar()">hello</button>
<script type="module" src="./strudel.js"></script>
</body>
</html>
+63 -3622
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -275,14 +275,14 @@ const generic_params = [
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
const _unionise = (func) =>
const _setter = (func) =>
function (...pats) {
return this.union(func(...pats));
return this.set(func(...pats));
};
generic_params.forEach(([type, name, description]) => {
controls[name] = (...pats) => _name(name, ...pats);
Pattern.prototype[name] = _unionise(controls[name]);
Pattern.prototype[name] = _setter(controls[name]);
});
export default controls;
+88 -38
View File
@@ -179,14 +179,16 @@ export class Pattern {
const query = function (state) {
const haps = [];
for (const hap_func of pat_func.query(state)) {
const event_vals = pat_val.query(state.setSpan(hap_func.part));
const event_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart()));
for (const hap_val of event_vals) {
const new_whole = hap_func.whole;
const new_part = hap_func.part.intersection_e(hap_val.part);
const new_value = hap_func.value(hap_val.value);
const new_context = hap_val.combineContext(hap_func);
const hap = new Hap(new_whole, new_part, new_value, new_context);
haps.push(hap);
const new_part = hap_func.part.intersection(hap_val.part);
if (new_part) {
const new_value = hap_func.value(hap_val.value);
const new_context = hap_val.combineContext(hap_func);
const hap = new Hap(new_whole, new_part, new_value, new_context);
haps.push(hap);
}
}
}
return haps;
@@ -200,14 +202,16 @@ export class Pattern {
const query = function (state) {
const haps = [];
for (const hap_val of pat_val.query(state)) {
const hap_funcs = pat_func.query(state.setSpan(hap_val.part));
const hap_funcs = pat_func.query(state.setSpan(hap_val.wholeOrPart()));
for (const hap_func of hap_funcs) {
const new_whole = hap_val.whole;
const new_part = hap_func.part.intersection_e(hap_val.part);
const new_value = hap_func.value(hap_val.value);
const new_context = hap_val.combineContext(hap_func);
const hap = new Hap(new_whole, new_part, new_value, new_context);
haps.push(hap);
const new_part = hap_func.part.intersection(hap_val.part);
if (new_part) {
const new_value = hap_func.value(hap_val.value);
const new_context = hap_val.combineContext(hap_func);
const hap = new Hap(new_whole, new_part, new_value, new_context);
haps.push(hap);
}
}
}
return haps;
@@ -243,9 +247,24 @@ export class Pattern {
);
}
_opleft(other, func) {
_opLeft(other, func) {
return this.fmap(func).appLeft(reify(other));
}
_opRight(other, func) {
return this.fmap(func).appRight(reify(other));
}
_opBoth(other, func) {
return this.fmap(func).appBoth(reify(other));
}
_opSqueeze(other, func) {
const otherPat = reify(other);
return this.fmap((a) => otherPat.fmap((b) => func(a)(b)))._squeezeJoin();
}
_opSqueezeFlip(other, func) {
const thisPat = this;
const otherPat = reify(other);
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a)))._squeezeJoin();
}
_asNumber(silent = false) {
return this._withEvent((event) => {
@@ -271,22 +290,6 @@ export class Pattern {
})._removeUndefineds();
}
add(other) {
return this._asNumber()._opleft(other, (a) => (b) => a + b);
}
sub(other) {
return this._asNumber()._opleft(other, (a) => (b) => a - b);
}
mul(other) {
return this._asNumber()._opleft(other, (a) => (b) => a * b);
}
div(other) {
return this._asNumber()._opleft(other, (a) => (b) => a / b);
}
round() {
return this._asNumber().fmap((v) => Math.round(v));
}
@@ -320,10 +323,6 @@ export class Pattern {
return this._fromBipolar().range(min, max);
}
union(other) {
return this._opleft(other, (a) => (b) => Object.assign({}, a, b));
}
_bindWhole(choose_whole, func) {
const pat_val = this;
const query = function (state) {
@@ -464,7 +463,7 @@ export class Pattern {
}
_compress(b, e) {
if (b > e || b > 1 || e > 1 || b < 0 || e < 0) {
if (b.gt(e) || b.gt(1) || e.gt(1) || b.lt(0) || e.lt(0)) {
return silence;
}
return this._fastGap(Fraction(1).div(e.sub(b)))._late(b);
@@ -496,6 +495,13 @@ export class Pattern {
return this._squeezeBind(func);
}
_striate(n) {
const slices = Array.from({ length: n }, (x, i) => i);
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
const slicePat = slowcat(...slice_objects);
return this.set(slicePat)._fast(n);
}
// cpm = cycles per minute
_cpm(cpm) {
return this._fast(cpm / 60);
@@ -558,7 +564,7 @@ export class Pattern {
}
_segment(rate) {
return this.struct(pure(true).fast(rate));
return this.struct(pure(true)._fast(rate));
}
invert() {
@@ -650,7 +656,7 @@ export class Pattern {
}
stutWith(times, time, func) {
return stack(...listRange(0, times - 1).map((i) => func(this.late(i * time), i)));
return stack(...listRange(0, times - 1).map((i) => func(this.late(Fraction(time).mul(i)), i)));
}
stut(times, feedback, time) {
@@ -659,7 +665,7 @@ export class Pattern {
// these might change with: https://github.com/tidalcycles/Tidal/issues/902
_echoWith(times, time, func) {
return stack(...listRange(0, times - 1).map((i) => func(this.late(i * time), i)));
return stack(...listRange(0, times - 1).map((i) => func(this.late(Fraction(time).mul(i)), i)));
}
_echo(times, time, feedback) {
@@ -717,6 +723,49 @@ export class Pattern {
}
}
// pattern composers
const composers = {
set: [
(a) => (b) => {
// If an object is involved, do a union, discarding matching keys from a.
// Otherwise, just return b.
if (a instanceof Object || b instanceof Object) {
if (!a instanceof Object) {
a = { value: a };
}
if (!b instanceof Object) {
b = { value: b };
}
return Object.assign({}, a, b);
}
return b;
},
id,
],
add: [(a) => (b) => a + b, (x) => x._asNumber()],
sub: [(a) => (b) => a - b, (x) => x._asNumber()],
mul: [(a) => (b) => a * b, (x) => x._asNumber()],
div: [(a) => (b) => a / b, (x) => x._asNumber()],
};
for (const [name, op] of Object.entries(composers)) {
Pattern.prototype[name] = function (...other) {
return op[1](this)._opLeft(sequence(other), op[0]);
};
Pattern.prototype[name + 'Flip'] = function (...other) {
return op[1](this)._opRight(sequence(other), op[0]);
};
Pattern.prototype[name + 'Sect'] = function (...other) {
return op[1](this)._opBoth(sequence(other), op[0]);
};
Pattern.prototype[name + 'Squeeze'] = function (...other) {
return op[1](this)._opSqueeze(sequence(other), op[0]);
};
Pattern.prototype[name + 'SqueezeFlip'] = function (...other) {
return op[1](this)._opSqueezeFlip(sequence(other), op[0]);
};
}
// methods of Pattern that get callable factories
Pattern.prototype.patternified = [
'apply',
@@ -732,6 +781,7 @@ Pattern.prototype.patternified = [
'linger',
'ply',
'segment',
'striate',
'slow',
'velocity',
];
@@ -918,7 +968,7 @@ export const slow = curry((a, pat) => pat.slow(a));
export const struct = curry((a, pat) => pat.struct(a));
export const sub = curry((a, pat) => pat.sub(a));
export const superimpose = curry((array, pat) => pat.superimpose(...array));
export const union = curry((a, pat) => pat.union(a));
export const set = curry((a, pat) => pat.set(a));
export const when = curry((binary, f, pat) => pat.when(binary, f));
// problem: curried functions with spread arguments must have pat at the beginning
+45 -5
View File
@@ -5,7 +5,7 @@ import { id } from './util.mjs';
export function steady(value) {
// A continuous value
return new Pattern((span) => Hap(undefined, span, value));
return new Pattern((state) => [new Hap(undefined, state.span, value)]);
}
export const signal = (func) => {
@@ -47,7 +47,7 @@ const timeToIntSeed = (x) => xorwise(Math.trunc(_frac(x / 300) * 536870912));
const intSeedToRand = (x) => (x % 536870912) / 536870912;
const timeToRand = (x) => intSeedToRand(timeToIntSeed(x));
const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x)));
const timeToRandsPrime = (seed, n) => {
const result = [];
@@ -60,8 +60,7 @@ const timeToRandsPrime = (seed, n) => {
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
export const rand2 = signal(timeToRand);
export const rand = rand2.fmap(Math.abs);
export const rand = signal(timeToRand);
export const _brandBy = (p) => rand.fmap((x) => x < p);
export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
@@ -71,14 +70,37 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
export const chooseWith = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat.range(0, xs.length).fmap((i) => xs[Math.floor(i)]);
return pat.range(0, xs.length).fmap((i) => xs[Math.floor(i)]).outerJoin();
};
export const choose = (...xs) => chooseWith(rand, xs);
const _wchooseWith = function (pat, ...pairs) {
const values = pairs.map((pair) => reify(pair[0]));
const weights = [];
let accum = 0;
for (const pair of pairs) {
accum += pair[1];
weights.push(accum);
}
const total = accum;
const match = function(r) {
const find = r * total;
return values[weights.findIndex((x) => x > find, weights)];
};
return pat.fmap(match);
};
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin()
export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
export const wchooseCycles = (...pairs) => _wchooseWith(rand, ...pairs).innerJoin();
export const perlinWith = (pat) => {
const pata = pat.fmap(Math.floor);
const patb = pat.fmap((t) => Math.floor(t) + 1);
@@ -142,6 +164,24 @@ Pattern.prototype.sometimesPre = function (func) {
return this._sometimesByPre(0.5, func);
};
Pattern.prototype._someCyclesBy = function (x, func) {
return stack(
this._degradeByWith(rand._segment(1), x),
func(this._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
);
};
Pattern.prototype.someCyclesBy = function (patx, func) {
const pat = this;
return reify(patx)
.fmap((x) => pat._someCyclesBy(x, func))
.innerJoin();
};
Pattern.prototype.someCycles = function (func) {
return this._someCyclesBy(0.5, func);
};
Pattern.prototype.often = function (func) {
return this.sometimesBy(0.75, func);
};
+104 -4
View File
@@ -36,6 +36,9 @@ import {
id,
ply,
} from '../index.mjs';
import { steady } from '../signal.mjs';
//import { Time } from 'tone';
import pkg from 'tone';
const { Time } = pkg;
@@ -47,6 +50,10 @@ const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, co
const third = Fraction(1, 3);
const twothirds = Fraction(2, 3);
const sameFirst = (a, b) => {
return assert.deepStrictEqual(a._sortEventsByPart().firstCycle(), b._sortEventsByPart().firstCycle());
};
describe('TimeSpan', function () {
describe('equals()', function () {
it('Should be equal to the same value', function () {
@@ -108,6 +115,18 @@ describe('Hap', function () {
assert.deepStrictEqual(state3, { incrementme: 12 });
});
});
describe('wholeOrPart()', () => {
const ts1 = new TimeSpan(Fraction(0), Fraction(1));
const ts0_5 = new TimeSpan(Fraction(0), Fraction(0.5));
const continuousHap = new Hap(undefined, ts1, 'hello');
const discreteHap = new Hap(ts1, ts0_5, 'hello');
it('Can pick a whole', () => {
assert.deepStrictEqual(discreteHap.wholeOrPart(), ts1);
});
it('Can pick a part', () => {
assert.deepStrictEqual(continuousHap.wholeOrPart(), ts1);
});
});
});
describe('Pattern', function () {
@@ -131,6 +150,33 @@ describe('Pattern', function () {
assert.equal(pure(3).add(pure(4)).query(st(0, 1))[0].value, 7);
});
});
describe('addFlip()', () => {
it('Can add things with structure from second pattern', () => {
sameFirst(sequence(1, 2).addFlip(4), sequence(5, 6).struct(true));
});
});
describe('addSqueeze()', () => {
it('Can add while squeezing the second pattern inside the events of the first', () => {
sameFirst(
sequence(1, [2, 3]).addSqueeze(sequence(10, 20, 30)),
sequence(
[11, 21, 31],
[
[12, 22, 32],
[13, 23, 33],
],
),
);
});
});
describe('addSqueezeFlip()', () => {
it('Can add while squeezing the first pattern inside the events of the second', () => {
sameFirst(
sequence(1, [2, 3]).addSqueezeFlip(10, 20, 30),
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
);
});
});
describe('sub()', function () {
it('Can subtract things', function () {
assert.equal(pure(3).sub(pure(4)).query(st(0, 1))[0].value, -1);
@@ -146,14 +192,50 @@ describe('Pattern', function () {
assert.equal(pure(3).div(pure(2)).firstCycle()[0].value, 1.5);
});
});
describe('union()', function () {
it('Can union things', function () {
describe('set()', function () {
it('Can set things in objects', function () {
assert.deepStrictEqual(
pure({ a: 4, b: 6 })
.union(pure({ c: 7 }))
.set(pure({ c: 7 }))
.firstCycle()[0].value,
{ a: 4, b: 6, c: 7 },
);
sameFirst(
sequence({ a: 1, b: 2 }, { a: 2, b: 2 }, { a: 3, b: 2 }).set({ a: 4, c: 5 }),
sequence({ a: 4, b: 2, c: 5 }).fast(3),
);
});
it('Can set things with plain values', function () {
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
});
describe('setFlip()', () => {
it('Can set things with structure from second pattern', () => {
sameFirst(sequence(1, 2).setFlip(4), pure(4).mask(true, true));
});
});
describe('setSqueeze()', () => {
it('Can squeeze one pattern inside the events of another', () => {
sameFirst(
sequence(1, [2, 3]).setSqueeze(sequence('a', 'b', 'c')),
sequence(
['a', 'b', 'c'],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
],
),
);
sameFirst(
sequence(1, [2, 3]).setSqueeze('a', 'b', 'c'),
sequence(
['a', 'b', 'c'],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
],
),
);
});
});
});
describe('stack()', function () {
@@ -399,7 +481,7 @@ describe('Pattern', function () {
});
});
describe('struct()', function () {
it('Can restructure a pattern', function () {
it('Can restructure a discrete pattern', function () {
assert.deepStrictEqual(sequence('a', 'b').struct(sequence(true, true, true)).firstCycle(), [
hap(ts(0, third), ts(0, third), 'a'),
hap(ts(third, twothirds), ts(third, 0.5), 'a'),
@@ -425,6 +507,12 @@ describe('Pattern', function () {
sequence('a', ['a', silence], 'a').firstCycle(),
);
});
it('Can structure a continuous pattern', () => {
assert.deepStrictEqual(
steady('a').struct(true, [true, true]).firstCycle(),
sequence('a', ['a', 'a']).firstCycle(),
);
});
});
describe('mask()', function () {
it('Can fragment a pattern', function () {
@@ -616,6 +704,18 @@ describe('Pattern', function () {
sequence(pure('a').fast(3), [pure('b').fast(3), pure('c').fast(3)]).firstCycle(),
);
});
it('Doesnt drop events in the 9th cycle', () => {
// fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
assert.equal(sequence(1, 2, 3).ply(2).early(8).firstCycle().length, 6);
});
});
describe('striate', () => {
it('Can striate(2)', () => {
sameFirst(
sequence({ sound: 'a' }).striate(2),
sequence({ sound: 'a', begin: 0, end: 0.5 }, { sound: 'a', begin: 0.5, end: 1 }),
);
});
});
describe('chop', () => {
it('Can _chop(2)', () => {
+17 -1
View File
@@ -1,5 +1,6 @@
import { strict as assert } from 'assert';
import { isNote, tokenizeNote, toMidi, mod, compose } from '../util.mjs';
import { pure } from '../pattern.mjs';
import { isNote, tokenizeNote, toMidi, fromMidi, mod, compose, getFrequency } from '../util.mjs';
describe('isNote', () => {
it('should recognize notes without accidentals', () => {
@@ -64,6 +65,21 @@ describe('toMidi', () => {
assert.equal(toMidi('C##3'), 50);
});
});
describe('fromMidi', () => {
it('should turn midi into frequency', () => {
assert.equal(fromMidi(69), 440);
assert.equal(fromMidi(57), 220);
});
});
describe('getFrequency', () => {
it('should turn midi into frequency', () => {
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
assert.equal(getFrequency(happify('a4')), 440);
assert.equal(getFrequency(happify('a3')), 220);
assert.equal(getFrequency(happify(440, { type: 'frequency' })), 440); // TODO: migrate when values are objects..
assert.equal(getFrequency(happify(432, { type: 'frequency' })), 432);
});
});
describe('mod', () => {
it('should work like regular modulo with positive numbers', () => {
+2 -3
View File
@@ -33,7 +33,7 @@ export class TimeSpan {
// (Note that the output timespan probably does not start *at* Time 0 --
// that only happens when the input Arc starts at an integral Time.)
const b = this.begin.cyclePos();
const e = b + (this.end - this.begin);
const e = b.add(this.end.sub(this.begin));
return new TimeSpan(b, e);
}
@@ -81,8 +81,7 @@ export class TimeSpan {
// Like 'sect', but raises an exception if the timespans don't intersect.
const result = this.intersection(other);
if (result == undefined) {
// TODO - raise exception
// raise ValueError(f'TimeSpan {self} and TimeSpan {other} do not intersect')
throw 'TimeSpans do not intersect';
}
return result;
}
+26 -7
View File
@@ -30,14 +30,33 @@ export const fromMidi = (n) => {
export const mod = (n, m) => (n < 0 ? mod(n + m, m) : n % m);
export const getPlayableNoteValue = (event) => {
let { value: note, context } = event;
// if value is number => interpret as midi number as long as its not marked as frequency
if (typeof note === 'number' && context.type !== 'frequency') {
note = fromMidi(event.value);
} else if (typeof note === 'string' && !isNote(note)) {
throw new Error('not a note: ' + note);
let { value, context } = event;
if (typeof value === 'object') {
value = value.n || value.note;
}
return note;
// if value is number => interpret as midi number as long as its not marked as frequency
if (typeof value === 'number' && context.type !== 'frequency') {
value = fromMidi(event.value);
} else if (typeof value === 'string' && !isNote(value)) {
throw new Error('not a note: ' + value);
}
return value;
};
export const getFrequency = (event) => {
let { value, context } = event;
// if value is number => interpret as midi number as long as its not marked as frequency
if (typeof value === 'object' && value.freq) {
return value.freq;
}
if (typeof value === 'number' && context.type !== 'frequency') {
value = fromMidi(event.value);
} else if (typeof value === 'string' && isNote(value)) {
value = fromMidi(toMidi(event.value));
} else if (typeof value !== 'number') {
throw new Error('not a note or frequency:' + value);
}
return value;
};
// rotate array by n steps (to the left)
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.0.6",
"version": "0.0.7",
"description": "Mini notation for strudel",
"main": "mini.mjs",
"type": "module",
@@ -25,6 +25,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.0.5",
"@strudel.cycles/eval": "^0.0.5",
"@strudel.cycles/tone": "^0.0.6"
}
+1
View File
@@ -21,6 +21,7 @@
"algorave"
],
"author": "Felix Roos <flix91@gmail.com>",
"contributors": ["Alex McLean <alex@slab.org>"],
"license": "GPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
+4
View File
@@ -0,0 +1,4 @@
# @strudel.cycles/serial
This package adds webserial functionality to strudel Patterns, for e.g. sending messages to arduino microcontrollers.
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@strudel.cycles/serial",
"version": "0.0.6",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Alex McLean <alex@slab.org>",
"license": "GPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {}
}
+52
View File
@@ -0,0 +1,52 @@
import { Pattern, isPattern } from '@strudel.cycles/core';
var serialWriter;
var choosing = false;
export async function getWriter(br=115200) {
if (choosing) {
return;
}
choosing = true;
if (serialWriter) {
return serialWriter;
}
if ('serial' in navigator) {
const port = await navigator.serial.requestPort();
await port.open({ baudRate: br });
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
const writer = textEncoder.writable.getWriter();
serialWriter = function (message) {
writer.write(message)
}
}
else {
throw('Webserial is not available in this browser.')
}
}
const latency = 0.1;
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
Pattern.prototype.serial = async function (...args) {
return this._withEvent((event) => {
if (!serialWriter) {
getWriter(...args);
}
const onTrigger = (time, event, currentTime) => {
var message = "";
if (typeof event.value === 'object') {
for (const [key, val] of Object.entries(event.value).flat()) {
message += `${key}:${val};`
}
}
else {
message = event.value;
}
const offset = (time - currentTime + latency) * 1000;
window.setTimeout(serialWriter, offset, message);
};
return event.setContext({ ...event.context, onTrigger });
});
};
-154
View File
@@ -117,157 +117,3 @@ export const highpass = (v) => new Filter(v, 'highpass');
export const adsr = (a, d = 0.1, s = 0.4, r = 0.01) => ({ envelope: { attack: a, decay: d, sustain: s, release: r } });
export const osc = (type) => ({ oscillator: { type } });
export const out = () => getDestination();
/*
You are entering experimental zone
*/
// the following code is an attempt to minimize tonejs code.. it is still an experiment
const chainable = function (instr) {
const _chain = instr.chain.bind(instr);
let chained = [];
instr.chain = (...args) => {
chained = chained.concat(args);
instr.disconnect(); // disconnect from destination / previous chain
return _chain(...chained, getDestination());
};
// shortcuts: chaining multiple won't work forn now.. like filter(1000).gain(0.5). use chain + native Tone calls instead
// instr.filter = (freq = 1000, type: BiquadFilterType = 'lowpass') =>
instr.filter = (freq = 1000, type = 'lowpass') =>
instr.chain(
new Filter(freq, type), // .Q.setValueAtTime(q, time);
);
instr.gain = (gain = 0.9) => instr.chain(new Gain(gain));
return instr;
};
// helpers
export const poly = (type) => {
const s = new PolySynth(Synth, { oscillator: { type } }).toDestination();
return chainable(s);
};
Pattern.prototype._poly = function (type = 'triangle') {
const instrumentConfig = {
oscillator: { type },
envelope: { attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01 },
};
if (!this.instrument) {
// create only once to keep the js heap happy
// this.instrument = new PolySynth(Synth, instrumentConfig).toDestination();
this.instrument = poly(type);
}
return this._withEvent((event) => {
const onTrigger = (time, event) => {
this.instrument.set(instrumentConfig);
this.instrument.triggerAttackRelease(event.value, event.duration, time);
};
return event.setContext({ ...event.context, instrumentConfig, onTrigger });
});
};
Pattern.prototype.define('poly', (type, pat) => pat.poly(type), { composable: true, patternified: true });
/*
You are entering danger zone
*/
// everything below is nice in theory, but not healthy for the JS heap, as nodes get recreated on every call
const getTrigger = (getChain, value) => (time, event) => {
const chain = getChain(); // make sure this returns a node that is connected toDestination // time
if (!isNote(value)) {
throw new Error('not a note: ' + value);
}
chain.triggerAttackRelease(value, event.duration, time);
setTimeout(() => {
// setTimeout is a little bit better compared to Transport.scheduleOnce
chain.dispose(); // mark for garbage collection
}, event.duration * 2000);
};
Pattern.prototype._synth = function (type = 'triangle') {
return this._withEvent((event) => {
const instrumentConfig = {
oscillator: { type },
envelope: { attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01 },
};
const getInstrument = () => {
const instrument = new Synth();
instrument.set(instrumentConfig);
return instrument;
};
const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value);
return event.setContext({ ...event.context, getInstrument, instrumentConfig, onTrigger });
});
};
Pattern.prototype.adsr = function (attack = 0.01, decay = 0.01, sustain = 0.6, release = 0.01) {
return this._withEvent((event) => {
if (!event.context.getInstrument) {
throw new Error('cannot chain adsr: need instrument first (like synth)');
}
const instrumentConfig = { ...event.context.instrumentConfig, envelope: { attack, decay, sustain, release } };
const getInstrument = () => {
const instrument = event.context.getInstrument();
instrument.set(instrumentConfig);
return instrument;
};
const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value);
return event.setContext({ ...event.context, getInstrument, instrumentConfig, onTrigger });
});
};
Pattern.prototype.chain = function (...effectGetters) {
return this._withEvent((event) => {
if (!event.context?.getInstrument) {
throw new Error('cannot chain: need instrument first (like synth)');
}
const chain = (event.context.chain || []).concat(effectGetters);
const getChain = () => {
const effects = chain.map((getEffect) => getEffect());
return event.context.getInstrument().chain(...effects, getDestination());
};
const onTrigger = getTrigger(getChain, event.value);
return event.setContext({ ...event.context, getChain, onTrigger, chain });
});
};
export const autofilter =
(freq = 1) =>
() =>
new AutoFilter(freq).start();
export const filter =
// (freq = 1, q = 1, type: BiquadFilterType = 'lowpass') =>
(freq = 1, q = 1, type = 'lowpass') =>
() =>
new Filter(freq, type); // .Q.setValueAtTime(q, time);
export const gain =
(gain = 0.9) =>
() =>
new Gain(gain);
Pattern.prototype._gain = function (g) {
return this.chain(gain(g));
};
// Pattern.prototype._filter = function (freq: number, q: number, type: BiquadFilterType = 'lowpass') {
Pattern.prototype._filter = function (freq, q, type = 'lowpass') {
return this.chain(filter(freq, q, type));
};
Pattern.prototype._autofilter = function (g) {
return this.chain(autofilter(g));
};
Pattern.prototype.define('synth', (type, pat) => pat.synth(type), { composable: true, patternified: true });
Pattern.prototype.define('gain', (gain, pat) => pat.synth(gain), { composable: true, patternified: true });
Pattern.prototype.define('filter', (cutoff, pat) => pat.filter(cutoff), { composable: true, patternified: true });
Pattern.prototype.define('autofilter', (cutoff, pat) => pat.filter(cutoff), { composable: true, patternified: true });
+1 -1
View File
@@ -26,4 +26,4 @@ scheduler.start()
```
A more sophisticated example can be found in [examples/repl.html](./examples/repl.html).
You can run it inside this directory with `npm run example`.
You can run it by opening the html file with your browser, or by clicking [this link](https://raw.githack.com/tidalcycles/strudel/main/packages/webaudio/examples/repl.html)
+7 -4
View File
@@ -3,7 +3,7 @@
<button id="stop" style="font-size: 2em">stop</button>
</div>
<textarea
style="font-size: 2em; background: #e8d565; color: #323230; height: 100%; width: 100%; outline: none; border: 0"
style="font-size: 2em; background: #bce865; color: #323230; height: 100%; width: 100%; outline: none; border: 0"
id="text"
spellcheck="false"
>
@@ -11,9 +11,12 @@ Loading...</textarea
>
<script type="module">
document.body.style = 'margin: 0';
import * as strudel from '@strudel.cycles/core';
// import * as strudel from '@strudel.cycles/core';
import * as strudel from '../../core/index.mjs';
import * as util from '../../core/util.mjs';
import '@strudel.cycles/core/euclid.mjs';
import { Scheduler, getAudioContext } from '@strudel.cycles/webaudio';
// import { Scheduler, getAudioContext } from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.0.4';
import { Scheduler, getAudioContext } from '../index.mjs';
const { cat, State, TimeSpan } = strudel;
Object.assign(window, strudel); // add strudel to eval scope
@@ -31,7 +34,7 @@ Loading...</textarea
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
.fast(3)
.velocity(.5)
.osc(cat('sawtooth','square').fast(2))
.wave(cat('sawtooth','square').fast(2))
.adsr(0.01,.02,.5,0.1)
.filter('lowshelf',800,25)
.out()`;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.0.2",
"version": "0.0.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
+4 -4
View File
@@ -1,13 +1,13 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.0.2",
"version": "0.0.6",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"directories": {
"example": "examples"
},
"scripts": {
"example": "parcel examples/repl.html"
"example": "npx parcel examples/repl.html"
},
"repository": {
"type": "git",
@@ -26,7 +26,7 @@
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"devDependencies": {
"parcel": "^2.4.1"
"dependencies": {
"@strudel.cycles/core": "^0.0.5"
}
}
+1
View File
@@ -1,4 +1,5 @@
import ClockWorker from './clockworker.mjs';
import { State, TimeSpan } from '@strudel.cycles/core';
class Scheduler {
worker;
+30 -17
View File
@@ -1,11 +1,13 @@
import { Pattern } from '@strudel.cycles/core';
import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
import { Tone } from '@strudel.cycles/tone';
let audioContext;
// let audioContext;
export const getAudioContext = () => {
if (!audioContext) {
return Tone.getContext().rawContext;
/* if (!audioContext) {
audioContext = new AudioContext();
}
return audioContext;
return audioContext; */
};
const lookahead = 0.2;
@@ -25,50 +27,61 @@ Pattern.prototype.withAudioNode = function (createAudioNode) {
return this._withEvent((event) => {
return event.setContext({
...event.context,
createAudioNode: (e) => createAudioNode(e, event.context.createAudioNode?.(event)),
createAudioNode: (t, e) => createAudioNode(t, e, event.context.createAudioNode?.(t, event)),
});
});
};
Pattern.prototype._osc = function (type) {
return this.withAudioNode((e) => {
Pattern.prototype._wave = function (type) {
return this.withAudioNode((t, e) => {
const osc = getAudioContext().createOscillator();
osc.type = type;
osc.frequency.value = e.value; // expects frequency..
osc.start(e.whole.begin.valueOf() + lookahead);
osc.stop(e.whole.end.valueOf() + lookahead); // release?
const f = getFrequency(e);
osc.frequency.value = f; // expects frequency..
const begin = t ?? e.whole.begin.valueOf() + lookahead;
const end = begin + e.duration;
osc.start(begin);
osc.stop(end); // release?
return osc;
});
};
Pattern.prototype.adsr = function (a = 0.01, d = 0.05, s = 1, r = 0.01) {
return this.withAudioNode((e, node) => {
return this.withAudioNode((t, e, node) => {
const velocity = e.context?.velocity || 1;
const envelope = adsr(a, d, s, r, velocity, e.whole.begin.valueOf() + lookahead, e.whole.end.valueOf() + lookahead);
const begin = t ?? e.whole.begin.valueOf() + lookahead;
const end = begin + e.duration + lookahead;
const envelope = adsr(a, d, s, r, velocity, begin, end);
node?.connect(envelope);
return envelope;
});
};
Pattern.prototype.filter = function (type = 'lowshelf', frequency = 1000, gain = 25) {
return this.withAudioNode((e, node) => {
Pattern.prototype._filter = function (type = 'lowpass', frequency = 1000) {
return this.withAudioNode((t, e, node) => {
const filter = getAudioContext().createBiquadFilter();
filter.type = type;
filter.frequency.value = frequency;
filter.gain.value = gain;
node?.connect(filter);
return filter;
});
};
Pattern.prototype.filter = function (type, frequency) {
return patternify2(Pattern.prototype._filter)(reify(type), reify(frequency), this);
};
Pattern.prototype.out = function () {
const master = getAudioContext().createGain();
master.gain.value = 0.1;
master.connect(getAudioContext().destination);
return this.withAudioNode((e, node) => {
return this.withAudioNode((t, e, node) => {
if (!node) {
console.warn('out: no source! call .osc() first');
}
node?.connect(master);
})._withEvent((event) => {
const onTrigger = (time, e) => e.context?.createAudioNode?.(time, e);
return event.setContext({ ...event.context, onTrigger });
});
};
Pattern.prototype.define('osc', (type, pat) => pat.osc(type), { patternified: true });
Pattern.prototype.define('wave', (type, pat) => pat.wave(type), { patternified: true });
+2
View File
@@ -30,6 +30,8 @@ import '@strudel.cycles/core/speak.mjs';
import '@strudel.cycles/tone/pianoroll.mjs';
import '@strudel.cycles/tone/draw.mjs';
import '@strudel.cycles/osc/osc.mjs';
import '@strudel.cycles/webaudio/webaudio.mjs';
import '@strudel.cycles/serial/serial.mjs';
import controls from '@strudel.cycles/core/controls.mjs';
extend(
+83 -1
View File
@@ -734,11 +734,93 @@ const bass = await sampler({
bell = bell.chain(vol(0.6).connect(delay),out());
"0".euclidLegato(3,8)
.echo(3, 1/16, .5)
.add(rand.range(0,12))
.echo(3, 1/16, (x,n)=>x.add((n+1)*2).velocity(1/(n+1)))
.velocity(rand.range(.5,1))
.legato(rand.range(.4,3))
.scale(slowcat('D minor pentatonic')).tone(bell)
.stack("<D2 A2 G2 F2>".euclidLegato(6,8,1).tone(bass.toDestination()))
.slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
export const waa = `"a4 [a3 c3] a3 c3"
.sub("<7 12>/2")
.off(1/8, add("12"))
.off(1/4, add("7"))
.legato(.5)
.slow(2)
.wave("sawtooth square")
.filter('lowpass', "<2000 1000 500>")
.out()`;
export const waar = `"a4 [a3 c3] a3 c3".color('#F9D649')
.sub("<7 12 5 12>".slow(2))
.off(1/4,x=>x.add(7).color("#FFFFFF #0C3AA1 #C63928"))
.off(1/8,x=>x.add(12).color('#215CB6'))
.slow(2)
.legato(sine.range(0.3, 2).slow(28))
.wave("sawtooth square".fast(2))
.filter('lowpass', cosine.range(500,4000).slow(16))
.out()
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})`;
export const hyperpop = `const lfo = cosine.slow(15);
const lfo2 = sine.slow(16);
const filter1 = x=>x.filter('lowpass', lfo2.range(300,3000));
const filter2 = x=>x.filter('highpass', lfo.range(1000,6000)).filter('lowpass',4000)
const scales = slowcat('D3 major', 'G3 major').slow(8)
const drums = await players({
bd: '344/344757_1676145-lq.mp3',
sn: '387/387186_7255534-lq.mp3',
hh: '561/561241_12517458-lq.mp3',
hh2:'44/44944_236326-lq.mp3',
hh3: '44/44944_236326-lq.mp3',
}, 'https://freesound.org/data/previews/')
stack(
"-7 0 -7 7".struct("x(5,8,2)").fast(2).sub(7)
.scale(scales).wave("sawtooth,square").velocity(.3).adsr(0.01,0.1,.5,0)
.apply(filter1),
"~@3 [<2 3>,<4 5>]"
.echo(8,1/16,.7)
.scale(scales)
.wave('square').velocity(.7).adsr(0.01,0.1,0).apply(filter1),
"6 5 4".add(14)
.superimpose(sub("5"))
.fast(1).euclidLegato(3,8)
.mask("<1 0@7>")
.fast(2)
.echo(32, 1/8, .9)
.scale(scales)
.wave("sawtooth")
.velocity(.2)
.adsr(.01,.5,0)
.apply(filter2)
//.echo(4,1/16,.5)
).out().stack(
stack(
"bd <~@7 [~ bd]>".fast(2),
"~ sn",
"[~ hh3]*2"
).tone(drums.chain(vol(.18),out())).fast(2)
).slow(2)
//.pianoroll({minMidi:20, maxMidi:160})
// strudel disable-highlighting`;
export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
.echoWith(4,1/4, (x,n)=>x
.add(n*7)
.velocity(1/(n+1))
.legato(1/(n+1)))
.velocity(perlin.range(.5,.9).slow(8))
.stack("[22 25]*3"
.legato(sine.range(.5,2).slow(8))
.velocity(sine.range(.4,.8).slow(5))
.echo(4,1/12,.5))
.scale(slowcat('D dorian','G mixolydian','C dorian','F mixolydian'))
.legato(1)
.slow(2)
.tone((await piano()).toDestination())
//.pianoroll({maxMidi:160})`;
-53
View File
@@ -590,59 +590,6 @@ Helper to set the envelope of a Tone.js instrument. Intended to be used with Ton
.tone(synth(adsr(0,.1,0,0)).chain(out()))`}
/>
### Experimental: Patternification
While the above methods work for static sounds, there is also the option to patternify tone methods.
This is currently experimental, because the performance is not stable, and audio glitches will appear after some time.
It would be great to get this to work without glitches though, because it is fun!
#### synth(type)
With .synth, you can create a synth with a variable wave type:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~"
.synth("<sawtooth8 square8>").slow(4)`}
/>
#### adsr(attack, decay?, sustain?, release?)
Chainable Envelope helper:
<MiniRepl
tune={`"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~".slow(4)
.synth('sawtooth16').adsr(0,.1,0,0)`}
/>
Due to having more than one argument, this method is not patternified.
#### filter(cuttoff)
Patternified filter:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~"
.synth('sawtooth16').filter("[500 2000]*8").slow(4)`}
/>
#### gain(value)
Patternified gain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~"
.synth('sawtooth16').gain("[.2 .8]*8").slow(4)`}
/>
#### autofilter(value)
Patternified autofilter:
<MiniRepl
tune={`"c2 c3"
.synth('sawtooth16').autofilter("<1 4 8>")`}
/>
## Tonal API
The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations.
-21
View File
@@ -1,21 +0,0 @@
// Snowpack Configuration File
// See all supported options: https://www.snowpack.dev/reference/configuration
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
/* ... */
},
plugins: [
/* ... */
],
packageOptions: {
/* ... */
},
devOptions: {
/* ... */
},
buildOptions: {
/* ... */
},
};