Merge branch 'main' into stateful-events

This commit is contained in:
alex 2022-02-25 10:59:41 +00:00
commit 1b6d3734cf
15 changed files with 753 additions and 523 deletions

View File

@ -53,7 +53,7 @@ Fraction.prototype.min = function(other) {
return this.lt(other) ? this : other;
};
Fraction.prototype.show = function() {
return this.n + "/" + this.d;
return this.s * this.n + "/" + this.d;
};
Fraction.prototype.or = function(other) {
return this.eq(0) ? other : this;
@ -276,6 +276,12 @@ class Pattern {
sub(other) {
return this._opleft(other, (a) => (b) => a - b);
}
mul(other) {
return this._opleft(other, (a) => (b) => a * b);
}
div(other) {
return this._opleft(other, (a) => (b) => a / b);
}
union(other) {
return this._opleft(other, (a) => (b) => Object.assign({}, a, b));
}
@ -316,6 +322,12 @@ class Pattern {
outerJoin() {
return this.outerBind(id);
}
_apply(func) {
return func(this);
}
layer(...funcs) {
return stack(...funcs.map((func) => func(this)));
}
_patternify(func) {
const pat = this;
const patterned = function(...args) {
@ -384,7 +396,7 @@ class Pattern {
return stack(with_pat, without_pat);
}
off(time_pat, func) {
return stack([this, func(this._early(time_pat))]);
return stack(this, func(this.late(time_pat)));
}
every(n, func) {
const pat = this;
@ -447,7 +459,7 @@ class Pattern {
return silence;
}
}
Pattern.prototype.patternified = ["fast", "slow", "early", "late"];
Pattern.prototype.patternified = ["apply", "fast", "slow", "early", "late"];
Pattern.prototype.factories = {pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
const silence = new Pattern((_) => []);
function pure(value) {

View File

@ -1346,4 +1346,4 @@ span.CodeMirror-selectedtext { background: none; }
color: white !important;
}
/*# sourceMappingURL=index.fd7d9b66.css.map */
/*# sourceMappingURL=index.a25424f4.css.map */

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

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
<link rel="stylesheet" type="text/css" href="/tutorial/index.fd7d9b66.css">
<link rel="stylesheet" type="text/css" href="/tutorial/index.a25424f4.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Strudel REPL">
<title>Strudel Tutorial</title>
@ -11,6 +11,6 @@
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/tutorial/index.acaced6c.js" defer=""></script>
<script src="/tutorial/index.c743025b.js" defer=""></script>
</body>
</html>

128
featurelist.md Normal file
View File

@ -0,0 +1,128 @@
# Tidal Features in Strudel
## Basics
- [ ] drawLine
- [ ] setcps
- [ ] naming patterns? block based evaluation?
- [ ] once
- [ ] 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 => strudel supports multiple args => is this layer?
- [ ] layer => is this like superimpose but with multiple args?
- [ ] steps ?
- [ ] iter, iter'
## Alteration
- [ ] range, rangex
- [ ] quantise
- [ ] ply
- [ ] stutter
- [ ] stripe, slowstripe
- [ ] palindrome = every(2, rev)
- [ ] trunc
- [ ] linger
- [ ] 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
- [ ] euclid, euclidInv, euclidFull
- [ ] ifp
## Time
- [x] fast
- [x] fastGap
- [x] slow
- [ ] hurry
- [ ] compress: is this compressSpan?
- [ ] zoom
- [ ] within
- [x] off
- [ ] rotL / rotR
- [x] rev
- [ ] 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
- [ ] segment
- [ ] discretise
## Randomness
- [ ] rand / irand
- [ ] perlin / perlinWith / perlin2 / perlin2With
## Composition
- [ ] ur
- [ ] seqP / seqPLoop

View File

@ -68,13 +68,13 @@ function App() {
useWebMidi({
ready: useCallback(({ outputs }) => {
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(' | ')}) to the pattern. `);
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `'${o.name}'`).join(' | ')}) to the pattern. `);
}, []),
connected: useCallback(({ outputs }) => {
pushLog(`Midi device connected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
pushLog(`Midi device connected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`);
}, []),
disconnected: useCallback(({ outputs }) => {
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`);
}, []),
});
@ -123,7 +123,7 @@ function App() {
</span>
</div>
{error && (
<div className={cx('absolute right-2 bottom-2', 'text-red-500')}>{error?.message || 'unknown error'}</div>
<div className={cx('absolute right-2 bottom-2 px-2', 'text-red-500')}>{error?.message || 'unknown error'}</div>
)}
</div>
<button

View File

@ -25,6 +25,13 @@ export default function enableWebMidi() {
const outputByName = (name: string) => WebMidi.getOutputByName(name);
Pattern.prototype.midi = function (output: string, channel = 1) {
if (output?.constructor?.name === 'Pattern') {
throw new Error(
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`
);
}
return this.fmap((value: any) => ({
...value,
onTrigger: (time: number, event: any) => {
@ -41,8 +48,8 @@ Pattern.prototype.midi = function (output: string, channel = 1) {
const device = output ? outputByName(output) : WebMidi.outputs[0];
if (!device) {
throw new Error(
`🔌 MIDI device ${output ? output : ''} not found. Use one of ${WebMidi.outputs
.map((o: any) => `"${o.name}"`)
`🔌 MIDI device '${output ? output : ''}' not found. Use one of ${WebMidi.outputs
.map((o: any) => `'${o.name}'`)
.join(' | ')}`
);
}

View File

@ -91,7 +91,9 @@ export default (code) => {
const isMarkable = isPatternArg(parents) || hasModifierCall(parent);
// add to location to pure(x) calls
if (node.type === 'CallExpression' && node.callee.name === 'pure') {
return reifyWithLocation(node.arguments[0].name, node.arguments[0], ast.locations, artificialNodes);
const literal = node.arguments[0];
const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]];
return reifyWithLocation(value + '', node.arguments[0], ast.locations, artificialNodes);
}
// replace pseudo note variables
if (node.type === 'IdentifierExpression') {
@ -339,4 +341,3 @@ function getLocationObject(node, locations) {
],
};
}

View File

@ -58,62 +58,28 @@ export const tetrisWithFunctions = `stack(sequence(
).slow(16)`;
export const tetris = `stack(
cat(
"e5 [b4 c5] d5 [c5 b4]",
"a4 [a4 c5] e5 [d5 c5]",
"b4 [~ c5] d5 e5",
"c5 a4 a4 ~",
"[~ d5] [~ f5] a5 [g5 f5]",
"e5 [~ c5] e5 [d5 c5]",
"b4 [b4 c5] d5 e5",
"c5 a4 a4 ~"
),
cat(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
cat(
"e5 [b4 c5] d5 [c5 b4]",
"a4 [a4 c5] e5 [d5 c5]",
"b4 [~ c5] d5 e5",
"c5 a4 a4 ~",
"[~ d5] [~ f5] a5 [g5 f5]",
"e5 [~ c5] e5 [d5 c5]",
"b4 [b4 c5] d5 e5",
"c5 a4 a4 ~"
),
cat(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
)
).slow(16)`;
export const tetrisRev = `stack(
cat(
"e5 [b4 c5] d5 [c5 b4]",
"a4 [a4 c5] e5 [d5 c5]",
"b4 [~ c5] d5 e5",
"c5 a4 a4 ~",
"[~ d5] [~ f5] a5 [g5 f5]",
"e5 [~ c5] e5 [d5 c5]",
"b4 [b4 c5] d5 e5",
"c5 a4 a4 ~",
).rev(),
cat(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
).rev()
).slow(16)`;
/*
.synth({
oscillator: {type: 'sawtooth'},
envelope: { attack: 0.1 }
}).filter(1200).gain(0.8)
*/
/* export const tetrisMini1 =
'"[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]".slow(16)';
*/
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
@ -132,52 +98,6 @@ export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[[a1 a2]*4]\`.slow(16)
`;
/* export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
[[~ d5] [~ f5] a5 [g5 f5]]
[e5 [~ c5] e5 [d5 c5]]
[b4 [b4 c5] d5 e5]
[c5 a4 a4 ~]],
[[e2 e3]*4]
[[a2 a3]*4]
[[g#2 g#3]*2 [e2 e3]*2]
[a2 a3 a2 a3 a2 a3 b1 c2]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]"\`)
`; */
// following syntax is not supported anymore
/* export const tetrisHaskell = `slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
[[~ d5] [~ f5] a5 [g5 f5]]
[e5 [~ c5] e5 [d5 c5]]
[b4 [b4 c5] d5 e5]
[c5 a4 a4 ~]],
[[e2 e3]*4]
[[a2 a3]*4]
[[g#2 g#3]*2 [e2 e3]*2]
[a2 a3 a2 a3 a2 a3 b1 c2]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]"
`; */
/*
export const tetrisHaskell = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]], [[e2 e3]*4] [[a2 a3]*4] [[g#2 g#3]*2 [e2 e3]*2] [a2 a3 a2 a3 a2 a3 b1 c2] [[d2 d3]*4] [[c2 c3]*4] [[b1 b2]*2 [e2 e3]*2] [[a1 a2]*4]"\`)`;
*/
export const spanish = `slowcat(
stack(c4,eb4,g4),
stack(bb3,d4,f4),
stack(ab3,c4,eb4),
stack(g3,b3,d4)
)`;
export const whirlyStrudel = `sequence(e4, [b2, b3], c4)
.every(4, fast(2))
.every(3, slow(1.5))
@ -321,26 +241,6 @@ export const magicSofa = `stack(
"<c2 f2 g2> <d2 g2 a2 e2>"
).slow(1).transpose.slowcat(0, 2, 3, 4)`;
/* export const confusedPhone = `stack("[g2 ~@1.3] [c3 ~@1.3]".slow(2))
.superimpose(
x => transpose(-12,x).late(0),
x => transpose(7,x).late(0.2),
x => transpose(10,x).late(0.4),
x => transpose(12,x).late(0.6),
x => transpose(24,x).late(0.8)
)
.scale(sequence('C dorian', 'C mixolydian').slow(4))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`; */
export const confusedPhoneDynamic = `stack("[g2 ~@1.3] [c3 ~@1.3]".slow(2))
.superimpose(
...[-12,7,10,12,24].slice(0,5).map((t,i,{length}) => x => transpose(t,x).late(i/length))
)
.scale(sequence('C dorian', 'C mixolydian').slow(4))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`;
export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
.superimpose(
transpose(-12).late(0),
@ -391,24 +291,25 @@ export const loungerave = `() => {
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"c1*2".tone(kick).mask("<x@7 ~>/8"),
"~ <x!7 [x@3 x]>".tone(snare).mask("<x@7 ~>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(1);
const synths = stack(
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2").edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b9>/2".struct("~ [x@0.1 ~]").voicings().edit(thru).every(2, early(1/4)).tone(keys).bypass("<0@7 1>/8".early(1/4))
"<Cm7 Bb7 Fm7 G7b9>/2".struct("~ [x@0.1 ~]").voicings().edit(thru).every(2, early(1/4)).tone(keys).mask("<x@7 ~>/8".early(1/4))
)
return stack(
drums,
synths
)
//.bypass("<0 1>*4")
//.mask("<x ~>*4")
//.early("0.25 0");
}`;
export const caverave = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
@ -418,8 +319,8 @@ export const caverave = `() => {
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"c1*2".tone(kick).mask("<x@7 ~>/8"),
"~ <x!7 [x@3 x]>".tone(snare).mask("<x@7 ~>/4"),
"[~ c4]*2".tone(hihat)
);
@ -431,9 +332,9 @@ export const caverave = `() => {
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4))
).apply(thru).tone(keys).mask("<~ x>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).apply(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().apply(thru).every(2, early(1/8)).tone(keys).mask("<x@7 ~>/8".early(1/4))
)
return stack(
drums.fast(2),
@ -441,15 +342,16 @@ export const caverave = `() => {
).slow(2);
}`;
export const callcenterhero = `()=>{
const bpm = 90;
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 s = scale(slowcat('F3 minor', 'Ab3 major', 'Bb3 dorian', 'C4 phrygian dominant').slow(4));
return stack(
"0 2".struct("<x ~> [x ~]").edit(s).scaleTranspose(stack(0,2)).tone(lead),
"<6 7 9 7>".struct("[~ [x ~]*2]*2").edit(s).scaleTranspose("[0,2] [2,4]".fast(2).every(4,rev)).tone(lead),
"-14".struct("[~ x@0.8]*2".early(0.01)).edit(s).tone(bass),
"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),
"-14".struct("[~ x@0.8]*2".early(0.01)).apply(s).tone(bass),
"c2*2".tone(membrane().chain(vol(0.6), out)),
"~ c2".tone(noise().chain(vol(0.2), out)),
"c4*4".tone(metal(adsr(0,.05,0)).chain(vol(0.03), out))
@ -457,3 +359,95 @@ export const callcenterhero = `()=>{
.slow(120 / bpm)
}
`;
export const primalEnemy = `()=>{
const f = fast("<1 <2 [4 8]>>");
return stack(
"c3,g3,c4".struct("[x ~]*2").apply(f).transpose("<0 <3 [5 [7 [9 [11 13]]]]>>"),
"c2 [c2 ~]*2".tone(synth(osc('sawtooth8')).chain(vol(0.8),out)),
"c1*2".tone(membrane().chain(vol(0.8),out))
).slow(1)
}`;
export const drums = `stack(
"c1*2".tone(membrane().chain(vol(0.8),out)),
"~ c3".tone(noise().chain(vol(0.8),out)),
"c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.015)).chain(vol(0.8),out))
)
`;
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 delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out);
const chorus = new Chorus(1,2.5,0.5).start();
return stack(
// melody
"<<10 7> <8 3>>/4".struct("x*3").apply(s)
.scaleTranspose("<0 3 2> <1 4 3>")
.superimpose(scaleTranspose(2).early(1/8))
.apply(t).tone(polysynth().set({
...osc('triangle4'),
...adsr(0,.08,0)
}).chain(vol(0.2).connect(delay),chorus,out)).mask("<~@3 x>/16".early(1/8)),
// pad
"[1,3]/4".scale('G3 minor pentatonic').apply(t).tone(polysynth().set({
...osc('square2'),
...adsr(0.1,.4,0.8)
}).chain(vol(0.2),chorus,out)).mask("<~ x>/32"),
// xylophone
"c3,g3,c4".struct("<x*2 x>").fast("<1 <2!3 [4 8]>>").apply(s).scaleTranspose("<0 <1 [2 [3 <4 5>]]>>").apply(t).tone(polysynth().set({
...osc('sawtooth4'),
...adsr(0,.1,0)
}).chain(vol(0.4).connect(delay),out)).mask("<x@3 ~>/16".early(1/8)),
// bass
"c2 [c2 ~]*2".scale('C hirajoshi').apply(t).tone(synth({
...osc('sawtooth6'),
...adsr(0,.03,.4,.1)
}).chain(vol(0.4),out)),
// kick
"<c1!3 [c1 ~]*2>*2".tone(membrane().chain(vol(0.8),out)),
// snare
"~ <c3!7 [c3 c3*2]>".tone(noise().chain(vol(0.8),out)),
// hihat
"c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.02)).chain(vol(0.5).connect(delay),out))
).slow(1)
}`;
export const sowhatelse = `()=> {
// mixer
const mix = (key) => vol({
chords: .2,
lead: 0.8,
bass: .4,
snare: .95,
kick: .9,
hihat: .35,
}[key]||0);
const delay = new FeedbackDelay(1/6, .3).chain(vol(.7), out);
const delay2 = new FeedbackDelay(1/6, .2).chain(vol(.15), out);
const chorus = new Chorus(1,2.5,0.5).start();
// instruments
const instr = (instrument) => ({
organ: polysynth().set({...osc('sawtooth4'), ...adsr(.01,.2,0)}).chain(mix('chords').connect(delay),out),
lead: polysynth().set({...osc('triangle4'),...adsr(0.01,.05,0)}).chain(mix('lead').connect(delay2), out),
bass: polysynth().set({...osc('sawtooth8'),...adsr(.02,.05,.3,.2)}).chain(mix('bass'),lowpass(3000), out),
pad: polysynth().set({...osc('square2'),...adsr(0.1,.4,0.8)}).chain(vol(0.15),chorus,out),
hihat: metal(adsr(0, .02, 0)).chain(mix('hihat'), out),
snare: noise(adsr(0, .15, 0.01)).chain(mix('snare'), lowpass(5000), out),
kick: membrane().chain(mix('kick'), out)
}[instrument]);
// harmony
const t = transpose("<0 0 1 0>/8");
const sowhat = scaleTranspose("0,3,6,9,11");
// track
return stack(
"[<0 4 [3 [2 1]]>]/4".struct("[x]*3").mask("[~ x ~]").scale('D5 dorian').off(1/6, scaleTranspose(-7)).off(1/3, scaleTranspose(-5)).apply(t).tone(instr('lead')).mask("<~ ~ x x>/8"),
"<<e3 [~@2 a3]> <[d3 ~] [c3 f3] g3>>".scale('D dorian').apply(sowhat).apply(t).tone(instr('organ')).mask("<x x x ~>/8"),
"<[d2 [d2 ~]*3]!3 <a1*2 c2*3 [a1 e2]>>".apply(t).tone(instr('bass')),
"c1*6".tone(instr('hihat')),
"~ c3".tone(instr('snare')),
"<[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")
).fast(6/8)
}`;

View File

@ -315,19 +315,13 @@ Like "\*" in mini notation, **fast** will play a pattern times the given number
With early, you can nudge a pattern to start earlier in time:
<MiniRepl tune={`cat(e5, pure(b4).late(0.5))`} />
Note that we have to wrap b4 in **pure** to be able to call a the pattern modifier **early**.
There is the shorthand **p** for this:
<MiniRepl tune={`cat(e5, b4.late(0.5))`} />
### late(cycles)
Like early, but in the other direction:
<MiniRepl tune={`cat(e5, b4.p.late(0.5))`} />
<MiniRepl tune={`cat(e5, b4.late(0.5))`} />
TODO: shouldn't it sound different?
@ -343,6 +337,8 @@ Will apply the given function every n cycles:
<MiniRepl tune={`cat(e5, pure(b4).every(4, late(0.5)))`} />
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:
<MiniRepl tune={`cat(e5, pure(b4).every(4, x => x.late(0.5)))`} />

View File

@ -125,13 +125,13 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent }: any) {
/* useWebMidi({
ready: useCallback(({ outputs }) => {
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(' | ')}) to the pattern. `);
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `'${o.name}'`).join(' | ')}) to the pattern. `);
}, []),
connected: useCallback(({ outputs }) => {
pushLog(`Midi device connected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
pushLog(`Midi device connected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`);
}, []),
disconnected: useCallback(({ outputs }) => {
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `'${o.name}'`).join(', ')}`);
}, []),
}); */

View File

@ -73,7 +73,7 @@ Fraction.prototype.min = function(other) {
}
Fraction.prototype.show = function () {
return this.n + "/" + this.d
return (this.s * this.n) + "/" + this.d
}
Fraction.prototype.or = function(other) {
@ -214,7 +214,7 @@ class Hap {
}
show() {
return "(" + (this.whole == undefined ? "~" : this.whole.show()) + ", " + this.part.show() + ", " + this.value + ")"
return "(" + (this.whole == undefined ? "~" : this.whole.show()) + ", " + this.part.show() + ", " + JSON.stringify(this.value?.value ?? this.value) + ")"
}
setContext(context) {
@ -441,6 +441,14 @@ class Pattern {
return this._opleft(other, a => b => a - b)
}
mul(other) {
return this._opleft(other, a => b => a * b)
}
div(other) {
return this._opleft(other, a => b => a / b)
}
union(other) {
return this._opleft(other, a => b => Object.assign({}, a, b))
}
@ -497,6 +505,14 @@ class Pattern {
return this.outerBind(id)
}
_apply(func) {
return func(this)
}
layer(...funcs) {
return stack(...funcs.map(func => func(this)))
}
_patternify(func) {
const pat = this
const patterned = function (...args) {
@ -595,7 +611,7 @@ class Pattern {
}
off(time_pat, func) {
return stack([this, func(this._early(time_pat))])
return stack(this, func(this.late(time_pat)))
}
every(n, func) {
@ -640,7 +656,7 @@ class Pattern {
const left = this.withValue(val => Object.assign({}, val, {pan: elem_or(val, "pan", 0.5) - by}))
const right = this.withValue(val => Object.assign({}, val, {pan: elem_or(val, "pan", 0.5) + by}))
return stack([left,func(right)])
return stack(left,func(right))
}
// is there a different name for those in tidal?
@ -658,9 +674,6 @@ class Pattern {
edit(...funcs) {
return stack(...funcs.map(func => func(this)));
}
pipe(func) {
return func(this);
}
_bypass(on) {
on = Boolean(parseInt(on));
@ -689,7 +702,7 @@ class Pattern {
}
// methods of Pattern that get callable factories
Pattern.prototype.patternified = ['fast', 'slow', 'early', 'late'];
Pattern.prototype.patternified = ['apply', 'fast', 'slow', 'early', 'late'];
// 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};
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)

View File

@ -81,6 +81,16 @@ describe('Pattern', function() {
assert.equal(pure(3).sub(pure(4)).query(st(0,1))[0].value, -1)
})
})
describe('mul()', function () {
it('Can multiply things', function() {
assert.equal(pure(3).mul(pure(2)).firstCycle[0].value, 6)
})
})
describe('div()', function () {
it('Can divide things', function() {
assert.equal(pure(3).div(pure(2)).firstCycle[0].value, 1.5)
})
})
describe('union()', function () {
it('Can union things', function () {
assert.deepStrictEqual(pure({a: 4, b: 6}).union(pure({c: 7})).firstCycle[0].value, {a: 4, b: 6, c: 7})
@ -343,4 +353,60 @@ describe('Pattern', function() {
)
})
})
describe("apply", () => {
it('Can apply a function', () => {
assert.deepStrictEqual(
sequence("a", "b")._apply(fast(2)).firstCycle,
sequence("a", "b").fast(2).firstCycle
)
}),
it('Can apply a pattern of functions', () => {
assert.deepStrictEqual(
sequence("a", "b").apply(fast(2)).firstCycle,
sequence("a", "b").fast(2).firstCycle
)
assert.deepStrictEqual(
sequence("a", "b").apply(fast(2),fast(3)).firstCycle,
sequence("a", "b").fast(2,3).firstCycle
)
})
})
describe("layer", () => {
it('Can layer up multiple functions', () => {
assert.deepStrictEqual(
sequence(1,2,3).layer(fast(2), pat => pat.add(3,4)).firstCycle,
stack(sequence(1,2,3).fast(2), sequence(1,2,3).add(3,4)).firstCycle
)
})
})
describe("early", () => {
it("Can shift an event earlier", () => {
assert.deepStrictEqual(
pure(30)._late(0.25).query(ts(1,2)),
[hap(ts(1/4,5/4), ts(1,5/4), 30), hap(ts(5/4,9/4), ts(5/4,2), 30)]
)
})
it("Can shift an event earlier, into negative time", () => {
assert.deepStrictEqual(
pure(30)._late(0.25).query(ts(0,1)),
[hap(ts(-3/4,1/4), ts(0,1/4), 30), hap(ts(1/4,5/4), ts(1/4,1), 30)]
)
})
})
describe("off", () => {
it("Can offset a transformed pattern from the original", () => {
assert.deepStrictEqual(
pure(30).off(0.25, add(2)).firstCycle,
stack(pure(30), pure(30).late(0.25).add(2)).firstCycle
)
})
})
describe("jux", () => {
it("Can juxtapose", () => {
assert.deepStrictEqual(
pure({a: 1}).jux(fast(2))._sortEventsByPart().firstCycle,
stack(pure({a:1, pan: 0}), pure({a:1, pan: 1}).fast(2))._sortEventsByPart().firstCycle
)
})
})
})