Merge branch 'more-functions' of github.com:tidalcycles/strudel into more-functions

This commit is contained in:
alex 2022-04-24 17:51:41 +01:00
commit 1a5c95d084
16 changed files with 230 additions and 229 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));
} }
@ -799,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

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

@ -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.');
} }