Merge pull request #90 from tidalcycles/more-functions

Fiddles with cat/stack
This commit is contained in:
Alex McLean 2022-04-25 21:10:21 +01:00 committed by GitHub
commit d2ec9768f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 281 additions and 246 deletions

View File

@ -1,131 +0,0 @@
# Tidal Features in Strudel
## Basics
- [ ] drawLine
- [ ] setcps
- [ ] naming patterns? block based evaluation?
- [ ] once
- [x] silence
- [x] hush
- [ ] panic
## Concatenation
https://tidalcycles.org/docs/patternlib/tour/concatenation
- [x] cat: is synonym to fastcat in strudel, while [in tidal, cat is slowcat](https://tidalcycles.org/docs/patternlib/tour/concatenation#cat)
- [x] fastcat
- [x] timeCat: why is this camel case?
- [ ] randcat
- [x] append: but is like fastAppend in tidal
- [ ] fastAppend
- [ ] slowAppend
- [ ] wedge
- [ ] brak
- [ ] flatpat
## Accumulation
- [ ] overlay => like stack? "The overlay function is similar to cat" => wrong?
- [ ] `<>` operator (=== overlay)
- [x] stack
- [x] superimpose
- [x] layer
- [ ] steps ?
- [x] iter
- [x] iter' = iterBack
## Alteration
- [ ] range, rangex
- [ ] quantise
- [ ] ply
- [x] stutter = echo
- [ ] stripe, slowstripe
- [ ] palindrome = every(2, rev)
- [ ] trunc
- [ ] linger
- [x] chunk, chunk'
- [ ] shuffle
- [ ] scramble
- [ ] rot
- [ ] step / step'
- [ ] lindenmeyer
- [ ] spread / spreadf / fastspread
- [ ] spreadChoose / spreadr
## conditions
- [x] every
- [ ] every'
- [ ] whenmod
- [ ] sometimes, sometimesBy, someCycles, someCyclesBy
- [ ] choose, chooseby, wchoose, wchooseby
- [x] struct
- [x] mask
- [ ] sew
- [ ] stitch
- [ ] select, selectF
- [ ] pickF
- [ ] squeeze
- [x] euclid, euclidLegato
- [ ] euclidInv, euclidFull
- [ ] ifp
## Time
- [x] fast
- [x] fastGap
- [x] slow
- [ ] hurry
- [ ] compress: is this compressSpan?
- [ ] zoom
- [ ] within
- [x] off
- [ ] rotL / rotR
- [x] rev
- [x] jux
- [ ] juxBy
- [ ] swingBy / swing
- [ ] ghost
- [ ] inside / outside
## Harmony & Melody
- [x] scale
- [ ] scaleList
- [ ] getScale
- [ ] toScale
- [ ] chordList
- [ ] arpeggiate
- [ ] arp
## Transitions
- [ ] anticipate / anticipateIn
- [ ] clutch / clutchIn
- [ ] histpan
- [ ] interpolate / interpolateIn
- [ ] jump / jumpIn / jumpIn' / jumpMod
- [ ] wait / waitT
- [ ] wash / washIn
- [ ] xfade / xfadeIn
## Sampling
- [ ] chop
- [ ] striate / striateBy
- [ ] loopAt
- [x] segment
- [ ] discretise
## Randomness
- [ ] rand / irand
- [ ] perlin / perlinWith / perlin2 / perlin2With
## Composition
- [ ] ur
- [ ] seqP / seqPLoop

View File

@ -0,0 +1,39 @@
import Fraction, { gcd } from './fraction.mjs';
function drawLine(pat, chars = 60) {
let cycle = 0;
let pos = Fraction(0);
let lines = [''];
let emptyLine = ''; // this will be the "reference" empty line, which will be copied into extra lines
while (lines[0].length < chars) {
const haps = pat.queryArc(cycle, cycle + 1);
const durations = haps.filter((hap) => hap.hasOnset()).map((hap) => hap.duration);
const charFraction = gcd(...durations);
const totalSlots = charFraction.inverse(); // number of character slots for the current cycle
lines = lines.map((line) => line + '|'); // add pipe character before each cycle
emptyLine += '|';
for (let i = 0; i < totalSlots; i++) {
const [begin, end] = [pos, pos.add(charFraction)];
const matches = haps.filter((hap) => hap.whole.begin.lte(begin) && hap.whole.end.gte(end));
const missingLines = matches.length - lines.length;
if (missingLines > 0) {
lines = lines.concat(Array(missingLines).fill(emptyLine));
}
lines = lines.map((line, i) => {
const hap = matches[i];
if (hap) {
const isOnset = hap.whole.begin.eq(begin);
const char = isOnset ? '' + hap.value : '-';
return line + char;
}
return line + '.';
});
emptyLine += '.';
pos = pos.add(charFraction);
}
cycle++;
}
return lines.join('\n');
}
export default drawLine;

View File

@ -27,7 +27,7 @@
const events = pattern.firstCycle(); // query first cycle const events = pattern.firstCycle(); // query first cycle
events.forEach((event) => { events.forEach((event) => {
ctx.fillStyle = event.value; ctx.fillStyle = event.value;
ctx.fillRect(event.whole.begin * canvas.width, 0, event.duration * canvas.width, canvas.height); ctx.fillRect(event.whole.begin * canvas.width, 0, event.duration.valueOf() * canvas.width, canvas.height);
}); });
} }
</script> </script>

View File

@ -72,6 +72,12 @@ const fraction = (n) => {
return Fraction(n); return Fraction(n);
}; };
export const gcd = (...fractions) => {
return fractions.reduce((gcd, fraction) => gcd.gcd(fraction), fraction(1));
};
fraction._original = Fraction;
export default fraction; export default fraction;
// "If you concern performance, cache Fraction.js objects and pass arrays/objects.“ // "If you concern performance, cache Fraction.js objects and pass arrays/objects.“

View File

@ -24,7 +24,7 @@ export class Hap {
} }
get duration() { get duration() {
return this.whole.end.sub(this.whole.begin).valueOf(); return this.whole.end.sub(this.whole.begin);
} }
wholeOrPart() { wholeOrPart() {

View File

@ -4,6 +4,7 @@ import Hap from './hap.mjs';
import State from './state.mjs'; import State from './state.mjs';
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs'; import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs';
import drawLine from './drawLine.mjs';
export class Pattern { export class Pattern {
// the following functions will get patternFactories as nested functions: // the following functions will get patternFactories as nested functions:
@ -563,6 +564,17 @@ export class Pattern {
return this._withContext((context) => ({ ...context, color })); return this._withContext((context) => ({ ...context, color }));
} }
log() {
return this._withEvent((e) => {
return e.setContext({ ...e.context, logs: (e.context?.logs || []).concat([e.show()]) });
});
}
drawLine() {
console.log(drawLine(this));
return this;
}
_segment(rate) { _segment(rate) {
return this.struct(pure(true)._fast(rate)); return this.struct(pure(true)._fast(rate));
} }
@ -597,10 +609,6 @@ export class Pattern {
return slowcatPrime(...pats); return slowcatPrime(...pats);
} }
append(other) {
return fastcat(...[this, other]);
}
rev() { rev() {
const pat = this; const pat = this;
const query = function (state) { const query = function (state) {
@ -643,14 +651,31 @@ export class Pattern {
return this.juxBy(1, func); return this.juxBy(1, func);
} }
// is there a different name for those in tidal?
stack(...pats) { stack(...pats) {
return stack(this, ...pats); return stack(this, ...pats);
} }
sequence(...pats) { sequence(...pats) {
return sequence(this, ...pats); return sequence(this, ...pats);
} }
// shorthand for sequence
seq(...pats) {
return sequence(this, ...pats);
}
cat(...pats) {
return cat(this, ...pats);
}
fastcat(...pats) {
return fastcat(this, ...pats);
}
slowcat(...pats) {
return slowcat(this, ...pats);
}
superimpose(...funcs) { superimpose(...funcs) {
return this.stack(...funcs.map((func) => func(this))); return this.stack(...funcs.map((func) => func(this)));
} }
@ -786,7 +811,20 @@ Pattern.prototype.patternified = [
'velocity', 'velocity',
]; ];
// methods that create patterns, which are added to patternified Pattern methods // methods that create patterns, which are added to patternified Pattern methods
Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr }; Pattern.prototype.factories = {
pure,
stack,
slowcat,
fastcat,
cat,
timeCat,
sequence,
seq,
polymeter,
pm,
polyrhythm,
pr,
};
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts) // the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
// Elemental patterns // Elemental patterns
@ -814,18 +852,23 @@ export function reify(thing) {
} }
return pure(thing); return pure(thing);
} }
// Basic functions for combining patterns // Basic functions for combining patterns
export function stack(...pats) { export function stack(...pats) {
const reified = pats.map((pat) => reify(pat)); // Array test here is to avoid infinite recursions..
const query = (state) => flatten(reified.map((pat) => pat.query(state))); pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
const query = (state) => flatten(pats.map((pat) => pat.query(state)));
return new Pattern(query); return new Pattern(query);
} }
export function slowcat(...pats) { export function slowcat(...pats) {
// Concatenation: combines a list of patterns, switching between them // Concatenation: combines a list of patterns, switching between them
// successively, one per cycle. // successively, one per cycle.
pats = pats.map(reify);
// Array test here is to avoid infinite recursions..
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
const query = function (state) { const query = function (state) {
const span = state.span; const span = state.span;
const pat_n = mod(span.begin.sam(), pats.length); const pat_n = mod(span.begin.sam(), pats.length);
@ -862,7 +905,7 @@ export function fastcat(...pats) {
} }
export function cat(...pats) { export function cat(...pats) {
return fastcat(...pats); return slowcat(...pats);
} }
export function timeCat(...timepats) { export function timeCat(...timepats) {
@ -878,6 +921,15 @@ export function timeCat(...timepats) {
return stack(...pats); return stack(...pats);
} }
export function sequence(...pats) {
return fastcat(...pats);
}
// shorthand for sequence
export function seq(...pats) {
return fastcat(...pats);
}
function _sequenceCount(x) { function _sequenceCount(x) {
if (Array.isArray(x)) { if (Array.isArray(x)) {
if (x.length == 0) { if (x.length == 0) {
@ -891,10 +943,6 @@ function _sequenceCount(x) {
return [reify(x), 1]; return [reify(x), 1];
} }
export function sequence(...xs) {
return _sequenceCount(xs)[0];
}
export function polymeterSteps(steps, ...args) { export function polymeterSteps(steps, ...args) {
const seqs = args.map((a) => _sequenceCount(a)); const seqs = args.map((a) => _sequenceCount(a));
if (seqs.length == 0) { if (seqs.length == 0) {
@ -941,7 +989,6 @@ export function pr(args) {
} }
export const add = curry((a, pat) => pat.add(a)); export const add = curry((a, pat) => pat.add(a));
export const append = curry((a, pat) => pat.append(a));
export const chunk = curry((a, pat) => pat.chunk(a)); export const chunk = curry((a, pat) => pat.chunk(a));
export const chunkBack = curry((a, pat) => pat.chunkBack(a)); export const chunkBack = curry((a, pat) => pat.chunkBack(a));
export const div = curry((a, pat) => pat.div(a)); export const div = curry((a, pat) => pat.div(a));

View File

@ -0,0 +1,50 @@
import { fastcat, stack, slowcat, silence, pure } from '../pattern.mjs';
import { strict as assert } from 'assert';
import drawLine from '../drawLine.mjs';
describe('drawLine', () => {
it('supports equal lengths', () => {
assert.equal(drawLine(fastcat(0), 4), '|0|0');
assert.equal(drawLine(fastcat(0, 1), 4), '|01|01');
assert.equal(drawLine(fastcat(0, 1, 2), 6), '|012|012');
});
it('supports unequal lengths', () => {
assert.equal(drawLine(fastcat(0, [1, 2]), 10), '|0-12|0-12');
assert.equal(drawLine(fastcat(0, [1, 2, 3]), 10), '|0--123|0--123');
assert.equal(drawLine(fastcat(0, 1, [2, 3]), 10), '|0-1-23|0-1-23');
});
it('supports unequal silence', () => {
assert.equal(drawLine(fastcat(0, silence, [1, 2]), 10), '|0-..12|0-..12');
});
it('supports polyrhythms', () => {
'0*2 1*3';
assert.equal(drawLine(fastcat(pure(0).fast(2), pure(1).fast(3)), 10), '|0--0--1-1-1-');
});
it('supports multiple lines', () => {
assert.equal(
drawLine(fastcat(0, stack(1, 2)), 10),
`|01|01|01|01
|.2|.2|.2|.2`,
);
assert.equal(
drawLine(fastcat(0, 1, stack(2, 3)), 10),
`|012|012|012
|..3|..3|..3`,
);
assert.equal(
drawLine(fastcat(0, stack(1, 2, 3)), 10),
`|01|01|01|01
|.2|.2|.2|.2
|.3|.3|.3|.3`,
);
assert.equal(
drawLine(fastcat(0, 1, stack(2, 3, 4)), 10),
`|012|012|012
|..3|..3|..3
|..4|..4|..4`,
);
});
it('supports unequal cycle lengths', () => {
assert.equal(drawLine(slowcat(0, [1, 2]), 10), `|0|12|0|12`);
});
});

View File

@ -0,0 +1,9 @@
import Fraction, { gcd } from '../fraction.mjs';
import { strict as assert } from 'assert';
describe('gcd', () => {
it('should work', () => {
const F = Fraction._original;
assert.equal(gcd(F(1 / 6), F(1 / 4)).toFraction(), '1/12');
});
});

View File

@ -247,6 +247,12 @@ describe('Pattern', function () {
['a', 'b', 'c'], ['a', 'b', 'c'],
); );
}); });
it('Can stack subpatterns', function () {
sameFirst(
stack('a', ['b','c']),
stack('a', sequence('b', 'c')),
);
});
}); });
describe('_fast()', function () { describe('_fast()', function () {
it('Makes things faster', function () { it('Makes things faster', function () {
@ -413,6 +419,12 @@ describe('Pattern', function () {
['c'], ['c'],
); );
}); });
it ('Can cat subpatterns', () => {
sameFirst(
slowcat('a', ['b','c']).fast(4),
sequence('a', ['b', 'c']).fast(2)
)
})
}); });
describe('rev()', function () { describe('rev()', function () {
it('Can reverse things', function () { it('Can reverse things', function () {

View File

@ -3,7 +3,7 @@ import { evaluate, extend } from '../evaluate.mjs';
import { mini } from '@strudel.cycles/mini'; import { mini } from '@strudel.cycles/mini';
import * as strudel from '@strudel.cycles/core'; import * as strudel from '@strudel.cycles/core';
const { cat } = strudel; const { fastcat } = strudel;
extend({ mini }, strudel); extend({ mini }, strudel);
@ -12,11 +12,11 @@ describe('evaluate', () => {
it('Should evaluate strudel functions', async () => { it('Should evaluate strudel functions', async () => {
assert.deepStrictEqual(await ev("pure('c3')"), ['c3']); assert.deepStrictEqual(await ev("pure('c3')"), ['c3']);
assert.deepStrictEqual(await ev('cat(c3)'), ['c3']); assert.deepStrictEqual(await ev('cat(c3)'), ['c3']);
assert.deepStrictEqual(await ev('cat(c3, d3)'), ['c3', 'd3']); assert.deepStrictEqual(await ev('fastcat(c3, d3)'), ['c3', 'd3']);
assert.deepStrictEqual(await ev('slowcat(c3, d3)'), ['c3']); assert.deepStrictEqual(await ev('slowcat(c3, d3)'), ['c3']);
}); });
it('Should be extendable', async () => { it('Should be extendable', async () => {
extend({ myFunction: (...x) => cat(...x) }); extend({ myFunction: (...x) => fastcat(...x) });
assert.deepStrictEqual(await ev('myFunction(c3, d3)'), ['c3', 'd3']); assert.deepStrictEqual(await ev('myFunction(c3, d3)'), ['c3', 'd3']);
}); });
it('Should evaluate simple double quoted mini notation', async () => { it('Should evaluate simple double quoted mini notation', async () => {

View File

@ -69,7 +69,7 @@ Pattern.prototype.midi = function (output, channel = 1) {
// await enableWebMidi() // await enableWebMidi()
device.playNote(note, channel, { device.playNote(note, channel, {
time, time,
duration: event.duration * 1000 - 5, duration: event.duration.valueOf() * 1000 - 5,
velocity, velocity,
}); });
}; };

View File

@ -54,14 +54,14 @@ Pattern.prototype.tone = function (instrument) {
note = getPlayableNoteValue(event); note = getPlayableNoteValue(event);
instrument.triggerAttack(note, time); instrument.triggerAttack(note, time);
} else if (instrument instanceof NoiseSynth) { } else if (instrument instanceof NoiseSynth) {
instrument.triggerAttackRelease(event.duration, time); // noise has no value instrument.triggerAttackRelease(event.duration.valueOf(), time); // noise has no value
} else if (instrument instanceof Piano) { } else if (instrument instanceof Piano) {
note = getPlayableNoteValue(event); note = getPlayableNoteValue(event);
instrument.keyDown({ note, time, velocity }); instrument.keyDown({ note, time, velocity });
instrument.keyUp({ note, time: time + event.duration, velocity }); instrument.keyUp({ note, time: time + event.duration.valueOf(), velocity });
} else if (instrument instanceof Sampler) { } else if (instrument instanceof Sampler) {
note = getPlayableNoteValue(event); note = getPlayableNoteValue(event);
instrument.triggerAttackRelease(note, event.duration, time, velocity); instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else if (instrument instanceof Players) { } else if (instrument instanceof Players) {
if (!instrument.has(event.value)) { if (!instrument.has(event.value)) {
throw new Error(`name "${event.value}" not defined for players`); throw new Error(`name "${event.value}" not defined for players`);
@ -69,10 +69,10 @@ Pattern.prototype.tone = function (instrument) {
const player = instrument.player(event.value); const player = instrument.player(event.value);
// velocity ? // velocity ?
player.start(time); player.start(time);
player.stop(time + event.duration); player.stop(time + event.duration.valueOf());
} else { } else {
note = getPlayableNoteValue(event); note = getPlayableNoteValue(event);
instrument.triggerAttackRelease(note, event.duration, time, velocity); instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} }
}; };
return event.setContext({ ...event.context, instrument, onTrigger }); return event.setContext({ ...event.context, instrument, onTrigger });

View File

@ -39,7 +39,7 @@ Pattern.prototype._wave = function (type) {
const f = getFrequency(e); const f = getFrequency(e);
osc.frequency.value = f; // expects frequency.. osc.frequency.value = f; // expects frequency..
const begin = t ?? e.whole.begin.valueOf() + lookahead; const begin = t ?? e.whole.begin.valueOf() + lookahead;
const end = begin + e.duration; const end = begin + e.valueOf();
osc.start(begin); osc.start(begin);
osc.stop(end); // release? osc.stop(end); // release?
return osc; return osc;
@ -49,7 +49,7 @@ Pattern.prototype.adsr = function (a = 0.01, d = 0.05, s = 1, r = 0.01) {
return this.withAudioNode((t, e, node) => { return this.withAudioNode((t, e, node) => {
const velocity = e.context?.velocity || 1; const velocity = e.context?.velocity || 1;
const begin = t ?? e.whole.begin.valueOf() + lookahead; const begin = t ?? e.whole.begin.valueOf() + lookahead;
const end = begin + e.duration + lookahead; const end = begin + e.duration.valueOf() + lookahead;
const envelope = adsr(a, d, s, r, velocity, begin, end); const envelope = adsr(a, d, s, r, velocity, begin, end);
node?.connect(envelope); node?.connect(envelope);
return envelope; return envelope;

View File

@ -44,8 +44,8 @@ export const markEvent = (editor) => (time, event) => {
//Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler... //Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler...
setTimeout(() => { setTimeout(() => {
marks.forEach((mark) => mark.clear()); marks.forEach((mark) => mark.clear());
// }, '+' + event.duration * 0.5); // }, '+' + event.duration.valueOf() * 0.5);
}, event.duration /* * 0.9 */ * 1000); }, event.duration.valueOf() /* * 0.9 */ * 1000);
}; };
let parenMark; let parenMark;

View File

@ -45,7 +45,7 @@ async function playStatic(code) {
if (!onTrigger) { if (!onTrigger) {
if (defaultSynth) { if (defaultSynth) {
const note = getPlayableNoteValue(event); const note = getPlayableNoteValue(event);
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity); defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else { } else {
throw new Error('no defaultSynth passed to useRepl.'); throw new Error('no defaultSynth passed to useRepl.');
} }

View File

@ -5,16 +5,16 @@ export const timeCatMini = `stack(
)`; )`;
export const timeCat = `stack( export const timeCat = `stack(
timeCat([3, c3], [1, stack(eb3, g3, cat(c4, d4).slow(2))]), timeCat([3, c3], [1, stack(eb3, g3, seq(c4, d4).slow(2))]),
cat(c2, g2), seq(c2, g2),
sequence( seq(
timeCat([5, eb4], [3, cat(f4, eb4, d4)]), timeCat([5, eb4], [3, seq(f4, eb4, d4)]),
cat(eb4, c4).slow(2) seq(eb4, c4).slow(2)
).slow(4) ).slow(4)
)`; )`;
export const shapeShifted = `stack( export const shapeShifted = `stack(
sequence( seq(
e5, [b4, c5], d5, [c5, b4], e5, [b4, c5], d5, [c5, b4],
a4, [a4, c5], e5, [d5, c5], a4, [a4, c5], e5, [d5, c5],
b4, [r, c5], d5, e5, b4, [r, c5], d5, e5,
@ -24,7 +24,7 @@ export const shapeShifted = `stack(
b4, [b4, c5], d5, e5, b4, [b4, c5], d5, e5,
c5, a4, a4, r, c5, a4, a4, r,
).rev(), ).rev(),
sequence( seq(
e2, e3, e2, e3, e2, e3, e2, e3, e2, e3, e2, e3, e2, e3, e2, e3,
a2, a3, a2, a3, a2, a3, a2, a3, a2, a3, a2, a3, a2, a3, a2, a3,
gs2, gs3, gs2, gs3, e2, e3, e2, e3, gs2, gs3, gs2, gs3, e2, e3, e2, e3,
@ -36,16 +36,16 @@ export const shapeShifted = `stack(
).rev() ).rev()
).slow(16)`; ).slow(16)`;
export const tetrisWithFunctions = `stack(sequence( /* export const tetrisWithFunctions = `stack(seq(
'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'), 'e5', seq('b4', 'c5'), 'd5', seq('c5', 'b4'),
'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'), 'a4', seq('a4', 'c5'), 'e5', seq('d5', 'c5'),
'b4', sequence(silence, 'c5'), 'd5', 'e5', 'b4', seq(r, 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', silence, 'c5', 'a4', 'a4', r,
sequence(silence, 'd5'), sequence(silence, 'f5'), 'a5', sequence('g5', 'f5'), seq(r, 'd5'), seq(r, 'f5'), 'a5', seq('g5', 'f5'),
'e5', sequence(silence, 'c5'), 'e5', sequence('d5', 'c5'), 'e5', seq(r, 'c5'), 'e5', seq('d5', 'c5'),
'b4', sequence('b4', 'c5'), 'd5', 'e5', 'b4', seq('b4', 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', silence), 'c5', 'a4', 'a4', r),
sequence( seq(
'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3',
'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3',
'g#2', 'g#3', 'g#2', 'g#3', 'e2', 'e3', 'e2', 'e3', 'g#2', 'g#3', 'g#2', 'g#3', 'e2', 'e3', 'e2', 'e3',
@ -55,10 +55,10 @@ export const tetrisWithFunctions = `stack(sequence(
'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3', 'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3',
'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2',
) )
).slow(16)`; ).slow(16)`; */
export const tetris = `stack( export const tetris = `stack(
cat( seq(
"e5 [b4 c5] d5 [c5 b4]", "e5 [b4 c5] d5 [c5 b4]",
"a4 [a4 c5] e5 [d5 c5]", "a4 [a4 c5] e5 [d5 c5]",
"b4 [~ c5] d5 e5", "b4 [~ c5] d5 e5",
@ -68,7 +68,7 @@ export const tetris = `stack(
"b4 [b4 c5] d5 e5", "b4 [b4 c5] d5 e5",
"c5 a4 a4 ~" "c5 a4 a4 ~"
), ),
cat( seq(
"e2 e3 e2 e3 e2 e3 e2 e3", "e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3", "a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3", "g#2 g#3 g#2 g#3 e2 e3 e2 e3",
@ -98,14 +98,14 @@ export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[[a1 a2]*4]\`.slow(16) [[a1 a2]*4]\`.slow(16)
`; `;
export const whirlyStrudel = `sequence(e4, [b2, b3], c4) export const whirlyStrudel = `seq(e4, [b2, b3], c4)
.every(4, fast(2)) .every(4, fast(2))
.every(3, slow(1.5)) .every(3, slow(1.5))
.fast(slowcat(1.25, 1, 1.5)) .fast(cat(1.25, 1, 1.5))
.every(2, _ => sequence(e4, r, e3, d4, r))`; .every(2, _ => seq(e4, r, e3, d4, r))`;
export const swimming = `stack( export const swimming = `stack(
cat( seq(
"~", "~",
"~", "~",
"~", "~",
@ -124,7 +124,7 @@ export const swimming = `stack(
"A5 [F5@2 C5] [D5@2 F5] F5", "A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2" "[C5@2 F5] [Bb5 A5 G5] F5@2"
), ),
cat( seq(
"[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]", "[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]", "[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]", "[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
@ -143,7 +143,7 @@ export const swimming = `stack(
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]", "[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]" "[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]"
), ),
cat( seq(
"[G3 G3 C3 E3]", "[G3 G3 C3 E3]",
"[F2 D2 G2 C2]", "[F2 D2 G2 C2]",
"[F2 D2 G2 C2]", "[F2 D2 G2 C2]",
@ -167,38 +167,38 @@ export const swimming = `stack(
export const giantSteps = `stack( export const giantSteps = `stack(
// melody // melody
cat( seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]", "[F#5 D5] [B4 G4] Bb4 [B4 A4]",
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]", "[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]", "Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 F#5]", "F#5 [G5 F5] Bb5 [F#5 F#5]",
), ),
// chords // chords
cat( seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]", "[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]", "[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]", "Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]" "B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).voicings(['E3', 'G4']), ).voicings(['E3', 'G4']),
// bass // bass
cat( seq(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]", "[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]",
"[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]", "[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]",
"[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]", "[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]",
"[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]" "[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]"
) )
).slow(20);`; ).slow(20)`;
export const giantStepsReggae = `stack( export const giantStepsReggae = `stack(
// melody // melody
cat( seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]", "[F#5 D5] [B4 G4] Bb4 [B4 A4]",
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]", "[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]", "Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 [F#5 ~@3]]", "F#5 [G5 F5] Bb5 [F#5 [F#5 ~@3]]",
), ),
// chords // chords
cat( seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]", "[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]", "[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]", "Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
@ -207,7 +207,7 @@ export const giantStepsReggae = `stack(
.struct("~ [x ~]".fast(4*8)) .struct("~ [x ~]".fast(4*8))
.voicings(['E3', 'G4']), .voicings(['E3', 'G4']),
// bass // bass
cat( seq(
"[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]", "[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]",
"[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]", "[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]",
"[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]", "[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]",
@ -220,13 +220,13 @@ export const transposedChordsHacked = `stack(
"c2 eb2 g2", "c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2) "Cm7".voicings(['g2','c4']).slow(2)
).transpose( ).transpose(
slowcat(1, 2, 3, 2).slow(2) cat(1, 2, 3, 2).slow(2)
).transpose(5)`; ).transpose(5)`;
export const scaleTranspose = `stack(f2, f3, c4, ab4) export const scaleTranspose = `stack(f2, f3, c4, ab4)
.scale(sequence('F minor', 'F harmonic minor').slow(4)) .scale(seq('F minor', 'F harmonic minor').slow(4))
.scaleTranspose(sequence(0, -1, -2, -3).slow(4)) .scaleTranspose(seq(0, -1, -2, -3).slow(4))
.transpose(sequence(0, 1).slow(16))`; .transpose(seq(0, 1).slow(16))`;
export const struct = `stack( export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]", "c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
@ -239,9 +239,9 @@ export const magicSofa = `stack(
.every(2, fast(2)) .every(2, fast(2))
.voicings(), .voicings(),
"<c2 f2 g2> <d2 g2 a2 e2>" "<c2 f2 g2> <d2 g2 a2 e2>"
).slow(1).transpose(slowcat(0, 2, 3, 4))`; ).slow(1).transpose(cat(0, 2, 3, 4))`;
// below doesn't work anymore due to constructor cleanup // below doesn't work anymore due to constructor cleanup
// ).slow(1).transpose.slowcat(0, 2, 3, 4)`; // ).slow(1).transpose.cat(0, 2, 3, 4)`;
export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]" export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
.superimpose( .superimpose(
@ -251,8 +251,8 @@ export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
transpose(12).late(0.3), transpose(12).late(0.3),
transpose(24).late(0.4) transpose(24).late(0.4)
) )
.scale(slowcat('C dorian', 'C mixolydian')) .scale(cat('C dorian', 'C mixolydian'))
.scaleTranspose(slowcat(0,1,2,1)) .scaleTranspose(cat(0,1,2,1))
.slow(2)`; .slow(2)`;
export const zeldasRescue = `stack( export const zeldasRescue = `stack(
@ -340,7 +340,7 @@ stack(
export const callcenterhero = `const bpm = 90; export const callcenterhero = `const bpm = 90;
const lead = polysynth().set({...osc('sine4'),...adsr(.004)}).chain(vol(0.15),out()) const lead = polysynth().set({...osc('sine4'),...adsr(.004)}).chain(vol(0.15),out())
const bass = fmsynth({...osc('sawtooth6'),...adsr(0.05,.6,0.8,0.1)}).chain(vol(0.6), out()); const bass = fmsynth({...osc('sawtooth6'),...adsr(0.05,.6,0.8,0.1)}).chain(vol(0.6), out());
const s = scale(slowcat('F3 minor', 'Ab3 major', 'Bb3 dorian', 'C4 phrygian dominant').slow(4)); const s = scale(cat('F3 minor', 'Ab3 major', 'Bb3 dorian', 'C4 phrygian dominant').slow(4));
stack( stack(
"0 2".struct("<x ~> [x ~]").apply(s).scaleTranspose(stack(0,2)).tone(lead), "0 2".struct("<x ~> [x ~]").apply(s).scaleTranspose(stack(0,2)).tone(lead),
"<6 7 9 7>".struct("[~ [x ~]*2]*2").apply(s).scaleTranspose("[0,2] [2,4]".fast(2).every(4,rev)).tone(lead), "<6 7 9 7>".struct("[~ [x ~]*2]*2").apply(s).scaleTranspose("[0,2] [2,4]".fast(2).every(4,rev)).tone(lead),
@ -379,7 +379,7 @@ stack(
`; `;
export const xylophoneCalling = `const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2) export const xylophoneCalling = `const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2)
const s = x => x.scale(slowcat('C3 minor pentatonic','G3 minor pentatonic').slow(4)) const s = x => x.scale(cat('C3 minor pentatonic','G3 minor pentatonic').slow(4))
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out()); const delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out());
const chorus = new Chorus(1,2.5,0.5).start(); const chorus = new Chorus(1,2.5,0.5).start();
stack( stack(
@ -412,7 +412,8 @@ stack(
"~ <c3!7 [c3 c3*2]>".tone(noise().chain(vol(0.8),out())), "~ <c3!7 [c3 c3*2]>".tone(noise().chain(vol(0.8),out())),
// hihat // hihat
"c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.02)).chain(vol(0.5).connect(delay),out())) "c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.02)).chain(vol(0.5).connect(delay),out()))
).slow(1)`; ).slow(1)
// strudel disable-highlighting`;
export const sowhatelse = `// mixer export const sowhatelse = `// mixer
const mix = (key) => vol({ const mix = (key) => vol({
@ -448,7 +449,8 @@ stack(
"~ c3".tone(instr('snare')), "~ c3".tone(instr('snare')),
"<[c1@5 c1] <c1 [[c1@2 c1] ~] [c1 ~ c1] [c1!2 ~ c1!3]>>".tone(instr('kick')), "<[c1@5 c1] <c1 [[c1@2 c1] ~] [c1 ~ c1] [c1!2 ~ c1!3]>>".tone(instr('kick')),
"[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8") "[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
).fast(6/8)`; ).fast(6/8)
// strudel disable-highlighting`;
export const barryHarris = `backgroundImage( export const barryHarris = `backgroundImage(
'https://media.npr.org/assets/img/2017/02/03/barryharris_600dpi_wide-7eb49998aa1af377d62bb098041624c0a0d1a454.jpg', 'https://media.npr.org/assets/img/2017/02/03/barryharris_600dpi_wide-7eb49998aa1af377d62bb098041624c0a0d1a454.jpg',
@ -481,7 +483,7 @@ const rhodes = await sampler({
}, 'https://loophole-letters.vercel.app/') }, 'https://loophole-letters.vercel.app/')
const bass = synth(osc('sawtooth8')).chain(vol(.5),out()) const bass = synth(osc('sawtooth8')).chain(vol(.5),out())
const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', slowcat('Db major','Db mixolydian')]).slow(4) const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', cat('Db major','Db mixolydian')])
stack( stack(
"<bd sn> <hh hh*2 hh*3>" "<bd sn> <hh hh*2 hh*3>"
@ -500,12 +502,12 @@ stack(
.tone(bass), .tone(bass),
).fast(3/2)`; ).fast(3/2)`;
export const wavyKalimba = `const delay = new FeedbackDelay(1/3, .5).chain(vol(.2), out()); export const wavyKalimba = `const delay = new FeedbackDelay(1/3, .5).chain(vol(.2), out())
let kalimba = await sampler({ let kalimba = await sampler({
C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3'
}) })
kalimba = kalimba.chain(vol(0.6).connect(delay),out()); kalimba = kalimba.chain(vol(0.6).connect(delay),out());
const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major']).slow(4); const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major'])
stack( stack(
"[0 2 4 6 9 2 0 -2]*3" "[0 2 4 6 9 2 0 -2]*3"
@ -573,7 +575,7 @@ stack(
chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8,-2)"), chords.rootNotes(2).struct("x(4,8,-2)"),
chords.rootNotes(4) chords.rootNotes(4)
.scale(slowcat('C minor','F dorian','G dorian','F# mixolydian')) .scale(cat('C minor','F dorian','G dorian','F# mixolydian'))
.struct("x(3,8,-2)".fast(2)) .struct("x(3,8,-2)".fast(2))
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4")) .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4"))
).slow(2) ).slow(2)
@ -581,7 +583,7 @@ stack(
.tone((await piano()).chain(out()))`; .tone((await piano()).chain(out()))`;
export const festivalOfFingers2 = `const chords = "<Cm7 Fm7 G7 F#7 >"; export const festivalOfFingers2 = `const chords = "<Cm7 Fm7 G7 F#7 >";
const scales = slowcat('C minor','F dorian','G dorian','F# mixolydian') const scales = cat('C minor','F dorian','G dorian','F# mixolydian')
stack( stack(
chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8)"), chords.rootNotes(2).struct("x(4,8)"),
@ -638,7 +640,7 @@ stack(
).cpm(78).slow(4).pianoroll() ).cpm(78).slow(4).pianoroll()
`; `;
export const goodTimes = `const scale = slowcat('C3 dorian','Bb2 major').slow(4); export const goodTimes = `const scale = cat('C3 dorian','Bb2 major').slow(4);
stack( stack(
"2*4".add(12).scale(scale) "2*4".add(12).scale(scale)
.off(1/8,x=>x.scaleTranspose("2")).fast(2) .off(1/8,x=>x.scaleTranspose("2")).fast(2)
@ -708,7 +710,7 @@ export const speakerman = `backgroundImage('https://external-content.duckduckgo.
{ className:'darken', style:'background-size:cover'}) { className:'darken', style:'background-size:cover'})
stack( stack(
"[g3,bb3,d4] [f3,a3,c4] [c3,e3,g3]@2".slow(2).late(.1), "[g3,bb3,d4] [f3,a3,c4] [c3,e3,g3]@2".slow(2).late(.1),
slowcat( cat(
'Baker man', 'Baker man',
'is baking bread', 'is baking bread',
'Baker man', 'Baker man',
@ -738,7 +740,7 @@ bell = bell.chain(vol(0.6).connect(delay),out());
.add(rand.range(0,12)) .add(rand.range(0,12))
.velocity(rand.range(.5,1)) .velocity(rand.range(.5,1))
.legato(rand.range(.4,3)) .legato(rand.range(.4,3))
.scale(slowcat('D minor pentatonic')).tone(bell) .scale(cat('D minor pentatonic')).tone(bell)
.stack("<D2 A2 G2 F2>".euclidLegato(6,8,1).tone(bass.toDestination())) .stack("<D2 A2 G2 F2>".euclidLegato(6,8,1).tone(bass.toDestination()))
.slow(6) .slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`; .pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
@ -768,7 +770,7 @@ export const hyperpop = `const lfo = cosine.slow(15);
const lfo2 = sine.slow(16); const lfo2 = sine.slow(16);
const filter1 = x=>x.filter('lowpass', lfo2.range(300,3000)); const filter1 = x=>x.filter('lowpass', lfo2.range(300,3000));
const filter2 = x=>x.filter('highpass', lfo.range(1000,6000)).filter('lowpass',4000) const filter2 = x=>x.filter('highpass', lfo.range(1000,6000)).filter('lowpass',4000)
const scales = slowcat('D3 major', 'G3 major').slow(8) const scales = cat('D3 major', 'G3 major').slow(8)
const drums = await players({ const drums = await players({
bd: '344/344757_1676145-lq.mp3', bd: '344/344757_1676145-lq.mp3',
@ -819,7 +821,7 @@ export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
.legato(sine.range(.5,2).slow(8)) .legato(sine.range(.5,2).slow(8))
.velocity(sine.range(.4,.8).slow(5)) .velocity(sine.range(.4,.8).slow(5))
.echo(4,1/12,.5)) .echo(4,1/12,.5))
.scale(slowcat('D dorian','G mixolydian','C dorian','F mixolydian')) .scale(cat('D dorian','G mixolydian','C dorian','F mixolydian'))
.legato(1) .legato(1)
.slow(2) .slow(2)
.tone((await piano()).toDestination()) .tone((await piano()).toDestination())

View File

@ -15,7 +15,8 @@ The best place to actually make music with Strudel is the [Strudel REPL](https:/
To get a taste of what Strudel can do, check out this track: To get a taste of what Strudel can do, check out this track:
<MiniRepl tune={`const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out()); <MiniRepl
tune={`const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out());
const kick = new MembraneSynth().chain(vol(.8), out()); const kick = new MembraneSynth().chain(vol(.8), out());
const snare = new NoiseSynth().chain(vol(.8), out()); const snare = new NoiseSynth().chain(vol(.8), out());
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out()); const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out());
@ -195,14 +196,14 @@ Internally, the mini notation will expand to use the actual functional JavaScrip
Notes are automatically available as variables: Notes are automatically available as variables:
<MiniRepl tune={`sequence(d4, fs4, a4)`} /> <MiniRepl tune={`seq(d4, fs4, a4)`} />
An important difference to the mini notation: An important difference to the mini notation:
For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name. For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name.
The above is the same as: The above is the same as:
<MiniRepl tune={`sequence('d4', 'f#4', 'a4')`} /> <MiniRepl tune={`seq('d4', 'f#4', 'a4')`} />
Using strings, you can also use "#". Using strings, you can also use "#".
@ -220,38 +221,35 @@ Most of the time, you won't need that function as input values of pattern creati
### cat(...values) ### cat(...values)
The given items are con**cat**enated spread evenly over one cycle: The given items are con**cat**enated, where each one takes one cycle:
<MiniRepl tune={`cat(e5, b4, d5, c5)`} /> <MiniRepl tune={`cat(e5, b4, [d5, c5])`} />
The function **fastcat** does the same as **cat**. - Square brackets will create a subsequence
- The function **slowcat** does the same as **cat**.
### sequence(...values) ### seq(...values)
Like **cat**, but allows nesting with arrays: Like **cat**, but the items are crammed into one cycle:
<MiniRepl tune={`sequence(e5, [b4, c5], d5, [c5, b4])`} /> <MiniRepl tune={`seq(e5, b4, [d5, c5])`} />
- Synonyms: **fastcat**, **sequence**
### stack(...values) ### stack(...values)
The given items are played at the same time at the same length: The given items are played at the same time at the same length:
<MiniRepl tune={`stack(g3,b3,e4)`} /> <MiniRepl tune={`stack(g3, b3, [e4, d4])`} />
### slowcat(...values) - Square Brackets will create a subsequence
Like cat, but each item has the length of one cycle:
<MiniRepl tune={`slowcat(e5, b4, d5, c5)`} />
<!-- ## slowcatPrime ? -->
### Nesting functions ### Nesting functions
You can nest functions inside one another: You can nest functions inside one another:
<MiniRepl <MiniRepl
tune={`slowcat( tune={`cat(
stack(g3,b3,e4), stack(g3,b3,e4),
stack(a3,c3,e4), stack(a3,c3,e4),
stack(b3,d3,fs4), stack(b3,d3,fs4),
@ -283,7 +281,7 @@ Plays the given items at the same time, within the same length:
We can write the same with **stack** and **cat**: We can write the same with **stack** and **cat**:
<MiniRepl tune={`stack(cat(e3, g3), cat(e4, g4, b4))`} /> <MiniRepl tune={`stack(seq(e3, g3), seq(e4, g4, b4))`} />
You can also use the shorthand **pr** instead of **polyrhythm**. You can also use the shorthand **pr** instead of **polyrhythm**.
@ -295,7 +293,7 @@ The following functions modify a pattern.
Like "/" in mini notation, **slow** will slow down a pattern over the given number of cycles: Like "/" in mini notation, **slow** will slow down a pattern over the given number of cycles:
<MiniRepl tune={`cat(e5, b4, d5, c5).slow(2)`} /> <MiniRepl tune={`seq(e5, b4, d5, c5).slow(2)`} />
The same in mini notation: The same in mini notation:
@ -305,19 +303,19 @@ The same in mini notation:
Like "\*" in mini notation, **fast** will play a pattern times the given number in one cycle: Like "\*" in mini notation, **fast** will play a pattern times the given number in one cycle:
<MiniRepl tune={`cat(e5, b4, d5, c5).fast(2)`} /> <MiniRepl tune={`seq(e5, b4, d5, c5).fast(2)`} />
### early(cycles) ### early(cycles)
With early, you can nudge a pattern to start earlier in time: With early, you can nudge a pattern to start earlier in time:
<MiniRepl tune={`cat(e5, b4.early(0.5))`} /> <MiniRepl tune={`seq(e5, b4.early(0.5))`} />
### late(cycles) ### late(cycles)
Like early, but in the other direction: Like early, but in the other direction:
<MiniRepl tune={`cat(e5, b4.late(0.5))`} /> <MiniRepl tune={`seq(e5, b4.late(0.5))`} />
<!-- TODO: shouldn't it sound different? --> <!-- TODO: shouldn't it sound different? -->
@ -325,19 +323,19 @@ Like early, but in the other direction:
Will reverse the pattern: Will reverse the pattern:
<MiniRepl tune={`cat(c3,d3,e3,f3).rev()`} /> <MiniRepl tune={`seq(c3,d3,e3,f3).rev()`} />
### every(n, func) ### every(n, func)
Will apply the given function every n cycles: Will apply the given function every n cycles:
<MiniRepl tune={`cat(e5, "b4".every(4, late(0.5)))`} /> <MiniRepl tune={`seq(e5, "b4".every(4, late(0.5)))`} />
<!-- TODO: should be able to do b4.every => like already possible with fast slow etc.. --> <!-- TODO: should be able to do b4.every => like already possible with fast slow etc.. -->
Note that late is called directly. This is a shortcut for: Note that late is called directly. This is a shortcut for:
<MiniRepl tune={`cat(e5, "b4".every(4, x => x.late(0.5)))`} /> <MiniRepl tune={`seq(e5, "b4".every(4, x => x.late(0.5)))`} />
<!-- TODO: should the function really run the first cycle? --> <!-- TODO: should the function really run the first cycle? -->
@ -612,7 +610,7 @@ Turns numbers into notes in the scale (zero indexed). Also sets scale for other
<MiniRepl <MiniRepl
tune={`"0 2 4 6 4 2" tune={`"0 2 4 6 4 2"
.scale(slowcat('C2 major', 'C2 minor').slow(2))`} .scale(seq('C2 major', 'C2 minor').slow(2))`}
/> />
Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3. Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3.

View File

@ -37,11 +37,14 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }) {
(time, event, currentTime) => { (time, event, currentTime) => {
try { try {
onEvent?.(event); onEvent?.(event);
if (event.context.logs?.length) {
event.context.logs.forEach(pushLog);
}
const { onTrigger, velocity } = event.context; const { onTrigger, velocity } = event.context;
if (!onTrigger) { if (!onTrigger) {
if (defaultSynth) { if (defaultSynth) {
const note = getPlayableNoteValue(event); const note = getPlayableNoteValue(event);
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity); defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else { } else {
throw new Error('no defaultSynth passed to useRepl.'); throw new Error('no defaultSynth passed to useRepl.');
} }