pattern.test.mjs - <short description TODO> Copyright (C) 2022 Strudel contributors - see https://codeberg.org/uzu/strudel/src/branch/main/packages/core/test/pattern.test.mjs This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.
7import Fraction from 'fraction.js';
9import { describe, it, expect, vi } from 'vitest'; 10 11import { 12 TimeSpan, 13 Hap, 14 State, 15 Pattern, 16 pure, 17 stack, 18 fastcat, 19 firstOf, 20 slowcat, 21 slowcatPrime, 22 cat, 23 sequence, 24 palindrome, 25 polymeter, 26 polyrhythm, 27 silence, 28 fast, 29 timeCat, 30 add, 31 sub, 32 mul, 33 div, 34 saw, 35 saw2, 36 isaw, 37 isaw2, 38 sine, 39 sine2, 40 square, 41 square2, 42 tri, 43 tri2, 44 id, 45 ply, 46 rev, 47 time, 48 run, 49 randrun, 50 binaryN, 51 pick, 52 stackLeft, 53 stackRight, 54 stackCentre, 55 stepcat, 56 sometimes, 57 expand, 58 arp, 59} from '../index.mjs'; 60 61import { log, logValues } from '../pattern.mjs'; 62 63import { steady } from '../signal.mjs'; 64 65import { n, s } from '../controls.mjs'; 66 67const st = (begin, end) => new State(ts(begin, end)); 68const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end)); 69const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context); 70 71const third = Fraction(1, 3); 72const twothirds = Fraction(2, 3); 73 74const sameFirst = (a, b) => { 75 return expect(a.sortHapsByPart().firstCycle()).toStrictEqual(b.sortHapsByPart().firstCycle()); 76}; 77 78describe('TimeSpan', () => { 79 describe('equals()', () => { 80 it('Should be equal to the same value', () => { 81 expect(new TimeSpan(0, 4).equals(new TimeSpan(0, 4))).toBe(true); 82 }); 83 }); 84 describe('splitCycles', () => { 85 it('Should split two cycles into two', () => { 86 expect(new TimeSpan(Fraction(0), Fraction(2)).spanCycles.length).toBe(2); 87 }); 88 }); 89 describe('intersection_e', () => { 90 var a = new TimeSpan(Fraction(0), Fraction(2)); 91 var b = new TimeSpan(Fraction(1), Fraction(3)); 92 var c = new TimeSpan(Fraction(1), Fraction(2)); 93 var d = new TimeSpan(Fraction(1), Fraction(2)); 94 it('Should create an intersection', () => { 95 expect(a.intersection_e(b).equals(c)).toBe(true); 96 }); 97 }); 98}); 99 100describe('Hap', () => { 101 describe('hasOnset()', () => { 102 it('True if part includes onset from whole', () => { 103 expect(new Hap(new TimeSpan(0, 1), new TimeSpan(0, 1), 'thing').hasOnset()).toBe(true); 104 }); 105 }); 106 var a = new Hap(new TimeSpan(Fraction(0), Fraction(0.5)), new TimeSpan(Fraction(0), Fraction(0.5)), 'a'); 107 var b = new Hap(new TimeSpan(Fraction(0), Fraction(0.5)), new TimeSpan(Fraction(0), Fraction(0.5)), 'b'); 108 var c = new Hap(new TimeSpan(Fraction(0), Fraction(0.25)), new TimeSpan(Fraction(0), Fraction(0.5)), 'c'); 109 var d = new Hap(undefined, new TimeSpan(Fraction(0), Fraction(0.5)), 'd'); 110 var e = new Hap(undefined, new TimeSpan(Fraction(0), Fraction(0.5)), 'e'); 111 describe('spanEquals', () => { 112 it('True if two haps have the same whole and part', () => { 113 expect(a.spanEquals(b)).toBe(true); 114 }); 115 it("False if two haps don't the same whole and part", () => { 116 expect(a.spanEquals(c)).toBe(false); 117 }); 118 it('True if two haps have the same part and undefined wholes', () => { 119 expect(d.spanEquals(e)).toBe(true); 120 }); 121 }); 122 describe('resolveState()', () => { 123 it('Can increment some state', () => { 124 const stateful_value = (state) => { 125 const newValue = state['incrementme']; 126 // TODO Does the state *need* duplicating here? 127 const newState = { ...state }; 128 newState['incrementme']++; 129 return [newState, newValue]; 130 }; 131 const state = { incrementme: 10 }; 132 const ev1 = new Hap(ts(0, 1), ts(0, 1), stateful_value, {}, true); 133 const [state2, ev2] = ev1.resolveState(state); 134 const [state3, ev3] = ev1.resolveState(state2); 135 expect(ev3).toStrictEqual(new Hap(ts(0, 1), ts(0, 1), 11, {}, false)); 136 expect(state3).toStrictEqual({ incrementme: 12 }); 137 }); 138 }); 139 describe('wholeOrPart()', () => { 140 const ts1 = new TimeSpan(Fraction(0), Fraction(1)); 141 const ts0_5 = new TimeSpan(Fraction(0), Fraction(0.5)); 142 const continuousHap = new Hap(undefined, ts1, 'hello'); 143 const discreteHap = new Hap(ts1, ts0_5, 'hello'); 144 it('Can pick a whole', () => { 145 expect(discreteHap.wholeOrPart()).toStrictEqual(ts1); 146 }); 147 it('Can pick a part', () => { 148 expect(continuousHap.wholeOrPart()).toStrictEqual(ts1); 149 }); 150 }); 151}); 152describe('Pattern', () => { 153 describe('pure', () => { 154 it('Can make a pattern', () => { 155 expect(pure('hello').query(st(0.5, 2.5)).length).toBe(3); 156 }); 157 it('Supports zero-width queries', () => { 158 expect(pure('hello').queryArc(0, 0).length).toBe(1); 159 }); 160 }); 161 describe('fmap()', () => { 162 it('Can add things', () => { 163 expect( 164 pure(3) 165 .fmap((x) => x + 4) 166 .firstCycle()[0].value, 167 ).toBe(7); 168 }); 169 }); 170 describe('out()', () => { 171 it('is an alias for set.out()', () => { 172 sameFirst(sequence(1, 2).out(5, 6, 7, 8), sequence(1, 2).set.out(5, 6, 7, 8)); 173 }); 174 }); 175 describe('add()', () => { 176 it('works as toplevel function', () => { 177 expect(add(pure(4), pure(5)).query(st(0, 1))[0].value).toBe(9); 178 }); 179 it('works as toplevel function, with bare values for arguments', () => { 180 expect(add(4, 5).query(st(0, 1))[0].value).toBe(9); 181 }); 182 it('can structure In()', () => { 183 expect(pure(3).add(pure(4)).query(st(0, 1))[0].value).toBe(7); 184 expect(pure(3).add.in(pure(4)).query(st(0, 1))[0].value).toBe(7); 185 }); 186 it('can structure Out()', () => { 187 sameFirst(sequence(1, 2).add.out(4), sequence(5, 6).struct(true)); 188 }); 189 it('can Mix() structure', () => { 190 expect(sequence(1, 2).add.mix(silence, 5, silence).firstCycle()).toStrictEqual([ 191 new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 6), 192 new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 7), 193 ]); 194 }); 195 it('can Reset() structure', () => { 196 sameFirst( 197 slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10) 198 .add.reset(20, 30) 199 .early(2), 200 sequence(26, 27, 36, 37), 201 ); 202 }); 203 it('can Restart() structure', () => { 204 sameFirst( 205 slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10) 206 .add.restart(20, 30) 207 .early(2), 208 sequence(21, 22, 31, 32), 209 ); 210 }); 211 it('can Squeeze() structure', () => { 212 sameFirst( 213 sequence(1, [2, 3]).add.squeeze(sequence(10, 20, 30)), 214 sequence( 215 [11, 21, 31], 216 [ 217 [12, 22, 32], 218 [13, 23, 33], 219 ], 220 ), 221 ); 222 }); 223 it('can SqueezeOut() structure', () => { 224 sameFirst( 225 sequence(1, [2, 3]).add.squeezeout(10, 20, 30), 226 sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]), 227 ); 228 }); 229 it('can add object patterns', () => { 230 sameFirst(n(sequence(1, [2, 3])).add(n(10)), n(sequence(11, [12, 13]))); 231 }); 232 }); 233 describe('keep()', () => { 234 it('can structure In()', () => { 235 expect(pure(3).keep(pure(4)).query(st(0, 1))[0].value).toBe(3); 236 expect(pure(3).keep.in(pure(4)).query(st(0, 1))[0].value).toBe(3); 237 }); 238 it('can structure Out()', () => { 239 sameFirst(sequence(1, 2).keep.out(4), sequence(1, 2).struct(true)); 240 }); 241 it('can Mix() structure', () => { 242 expect(sequence(1, 2).keep.mix(silence, 5, silence).firstCycle()).toStrictEqual([ 243 new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1), 244 new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2), 245 ]); 246 }); 247 it('can Reset() structure', () => { 248 sameFirst( 249 slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10) 250 .keep.reset(20, 30) 251 .early(2), 252 sequence(6, 7, 6, 7), 253 ); 254 }); 255 it('can Restart() structure', () => { 256 sameFirst( 257 slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10) 258 .keep.restart(20, 30) 259 .early(2), 260 sequence(1, 2, 1, 2), 261 ); 262 }); 263 it('can Squeeze() structure', () => { 264 sameFirst( 265 sequence(1, [2, 3]).keep.squeeze(sequence(10, 20, 30)), 266 sequence( 267 [1, 1, 1], 268 [ 269 [2, 2, 2], 270 [3, 3, 3], 271 ], 272 ), 273 ); 274 }); 275 it('can SqueezeOut() structure', () => { 276 sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]])); 277 }); 278 }); 279 describe('keepif()', () => { 280 it('can structure In()', () => { 281 sameFirst(sequence(3, 4).keepif(true, false), sequence(3, silence)); 282 sameFirst(sequence(3, 4).keepif.in(true, false), sequence(3, silence)); 283 }); 284 it('can structure Out()', () => { 285 sameFirst(pure(1).keepif.out(true, false), sequence(1, silence)); 286 }); 287 it('can Mix() structure', () => { 288 expect(sequence(1, 2).keepif.mix(false, true, false).firstCycle()).toStrictEqual([ 289 new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1), 290 new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2), 291 ]); 292 }); 293 it('can Reset() structure', () => { 294 sameFirst( 295 slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10) 296 .keepif.reset(false, true) 297 .early(2), 298 sequence(silence, silence, 6, 7), 299 ); 300 }); 301 it('can Restart() structure', () => { 302 sameFirst( 303 slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10) 304 .keepif.restart(false, true) 305 .early(2), 306 sequence(silence, silence, 1, 2), 307 ); 308 }); 309 it('can Squeeze() structure', () => { 310 sameFirst( 311 sequence(1, [2, 3]).keepif.squeeze(sequence(true, true, false)), 312 sequence( 313 [1, 1, silence], 314 [ 315 [2, 2, silence], 316 [3, 3, silence], 317 ], 318 ), 319 ); 320 }); 321 it('can SqueezeOut() structure', () => { 322 sameFirst(sequence(1, [2, 3]).keepif.squeezeout(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence)); 323 }); 324 }); 325 describe('sub()', () => { 326 it('Can subtract things', () => { 327 expect(pure(3).sub(pure(4)).query(st(0, 1))[0].value).toBe(-1); 328 }); 329 }); 330 describe('mul()', () => { 331 it('Can multiply things', () => { 332 expect(pure(3).mul(pure(2)).firstCycle()[0].value).toBe(6); 333 }); 334 }); 335 describe('div()', () => { 336 it('Can divide things', () => { 337 expect(pure(3).div(pure(2)).firstCycle()[0].value).toBe(1.5); 338 }); 339 }); 340 describe('set()', () => { 341 it('Can set things in objects', () => { 342 expect( 343 pure({ a: 4, b: 6 }) 344 .set(pure({ c: 7 })) 345 .firstCycle()[0].value, 346 ).toStrictEqual({ 347 a: 4, 348 b: 6, 349 c: 7, 350 }); 351 352 sameFirst( 353 sequence({ a: 1, b: 2 }, { a: 2, b: 2 }, { a: 3, b: 2 }).set({ a: 4, c: 5 }), 354 sequence({ a: 4, b: 2, c: 5 }).fast(3), 355 ); 356 }); 357 it('Can set things with plain values', () => { 358 sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3)); 359 }); 360 describe('setOut()', () => { 361 it('Can set things with structure from second pattern', () => { 362 sameFirst(sequence(1, 2).set.out(4), pure(4).mask(true, true)); 363 }); 364 }); 365 describe('setSqueeze()', () => { 366 it('Can squeeze one pattern inside the haps of another', () => { 367 sameFirst( 368 sequence(1, [2, 3]).set.squeeze(sequence('a', 'b', 'c')), 369 sequence( 370 ['a', 'b', 'c'], 371 [ 372 ['a', 'b', 'c'], 373 ['a', 'b', 'c'], 374 ], 375 ), 376 ); 377 sameFirst( 378 sequence(1, [2, 3]).set.squeeze('a', 'b', 'c'), 379 sequence( 380 ['a', 'b', 'c'], 381 [ 382 ['a', 'b', 'c'], 383 ['a', 'b', 'c'], 384 ], 385 ), 386 ); 387 }); 388 }); 389 }); 390 describe('stack()', () => { 391 it('Can stack things', () => { 392 expect( 393 stack(pure('a'), pure('b'), pure('c')) 394 .firstCycle() 395 .map((h) => h.value), 396 ).toStrictEqual(['a', 'b', 'c']); 397 }); 398 it('Can stack subpatterns', () => { 399 sameFirst(stack('a', ['b', 'c']), stack('a', sequence('b', 'c'))); 400 }); 401 }); 402 describe('_fast()', () => { 403 it('Makes things faster', () => { 404 expect(pure('a')._fast(2).firstCycle().length).toBe(2); 405 }); 406 }); 407 describe('_fastGap()', () => { 408 it('Makes things faster, with a gap', () => { 409 expect(sequence('a', 'b', 'c')._fastGap(2).firstCycle()).toStrictEqual( 410 sequence(['a', 'b', 'c'], silence).firstCycle(), 411 ); 412 expect(sequence('a', 'b', 'c')._fastGap(3).firstCycle()).toStrictEqual( 413 sequence(['a', 'b', 'c'], silence, silence).firstCycle(), 414 ); 415 }); 416 it('Makes things faster, with a gap, when speeded up further', () => { 417 expect(sequence('a', 'b', 'c')._fastGap(2).fast(2).firstCycle()).toStrictEqual( 418 sequence(['a', 'b', 'c'], silence, ['a', 'b', 'c'], silence).firstCycle(), 419 ); 420 }); 421 it('copes with breaking up events across cycles', () => { 422 expect(pure('a').slow(2)._fastGap(2).setContext({}).query(st(0, 2))).toStrictEqual([ 423 hap(ts(0, 1), ts(0, 0.5), 'a'), 424 hap(ts(0.5, 1.5), ts(1, 1.5), 'a'), 425 ]); 426 }); 427 }); 428 describe('_compressSpan()', () => { 429 it('Can squash cycles of a pattern into a given timespan', () => { 430 expect(pure('a')._compressSpan(ts(0.25, 0.5)).firstCycle()).toStrictEqual( 431 sequence(silence, 'a', silence, silence).firstCycle(), 432 ); 433 }); 434 }); 435 describe('fast()', () => { 436 it('Makes things faster', () => { 437 expect(pure('a').fast(2).firstCycle().length).toBe(2); 438 }); 439 it('Makes things faster, with a pattern of factors', () => { 440 expect(pure('a').fast(sequence(1, 4)).firstCycle().length).toBe(3); 441 expect(pure('a').fast(sequence(1, 4)).firstCycle()).toStrictEqual( 442 stack(pure('a').fast(sequence(1, silence)), sequence(silence, ['a', 'a'])).firstCycle(), 443 ); 444 }); 445 it('defaults to accepting sequences', () => { 446 expect(sequence('a', 'b', 'c').fast(sequence(1.5, 2)).sortHapsByPart().firstCycle()).toStrictEqual( 447 sequence('a', 'b', 'c').fast(1.5, 2).sortHapsByPart().firstCycle(), 448 ); 449 }); 450 it('works as a static function', () => { 451 expect(sequence(1, 2, 3).fast(1, 2).firstCycle()).toStrictEqual( 452 fast(sequence(1, 2), sequence(1, 2, 3)).firstCycle(), 453 ); 454 }); 455 it('works as a curried static function', () => { 456 expect(sequence(1, 2, 3).fast(1, 2).firstCycle()).toStrictEqual( 457 fast(sequence(1, 2))(sequence(1, 2, 3)).firstCycle(), 458 ); 459 }); 460 }); 461 462 describe('_slow()', () => { 463 it('Makes things slower', () => { 464 expect(pure('a')._slow(2).firstCycle()[0]).toStrictEqual(hap(ts(0, 2), ts(0, 1), 'a')); 465 466 const pat = sequence(pure('c3'), pure('eb3')._slow(2)); // => try mini('c3 eb3/2') in repl 467 468 expect(pat.query(st(0, 1))[1]).toStrictEqual(hap(ts(0.5, 1.5), ts(1 / 2, 1), 'eb3'));
the following test fails assert.deepStrictEqual( pat.query(ts(1,2))[1], undefined ) expecting [c3 eb3] [c3 ~] what happens [c3 eb3] [c3 eb3] notable examples: mini('[c3 g3]/2 eb3') always plays [c3 eb3] mini('eb3 [c3 g3]/2 ') always plays [c3 g3]
479 }); 480 it('Supports zero-length queries', () => { 481 expect(steady('a')._slow(1).queryArc(0, 0)).toStrictEqual(steady('a').queryArc(0, 0)); 482 }); 483 }); 484 describe('slow()', () => { 485 it('Supports zero-length queries', () => { 486 expect(steady('a').slow(1).setContext({}).queryArc(0, 0)).toStrictEqual( 487 steady('a').setContext({}).queryArc(0, 0), 488 ); 489 }); 490 }); 491 describe('inside', () => { 492 it('can rev inside a cycle', () => { 493 sameFirst(sequence('a', 'b', 'c', 'd').inside(2, rev), sequence('b', 'a', 'd', 'c')); 494 }); 495 }); 496 describe('outside', () => { 497 it('can rev outside a cycle', () => { 498 sameFirst(sequence('a', 'b', 'c', 'd')._slow(2).outside(2, rev), sequence('d', 'c')); 499 }); 500 }); 501 describe('_filterValues()', () => { 502 it('Filters true', () => { 503 expect( 504 pure(true) 505 .filterValues((x) => x) 506 .firstCycle().length, 507 ).toBe(1); 508 }); 509 }); 510 describe('when()', () => { 511 it('Always faster', () => { 512 expect( 513 pure('a') 514 .when(pure(true), (x) => x._fast(2)) 515 .firstCycle().length, 516 ).toBe(2); 517 }); 518 it('Never faster', () => { 519 expect( 520 pure('a') 521 .when(pure(false), (x) => x._fast(2)) 522 .firstCycle().length, 523 ).toBe(1); 524 }); 525 it('Can alternate', () => { 526 expect( 527 pure(10) 528 .when(slowcat(true, false), (x) => x.add(3)) 529 .fast(4) 530 .sortHapsByPart() 531 .firstCycle(), 532 ).toStrictEqual(fastcat(13, 10, 13, 10).firstCycle()); 533 }); 534 }); 535 describe('fastcat()', () => { 536 it('Can concatenate two things', () => { 537 expect( 538 fastcat(pure('a'), pure('b')) 539 .firstCycle() 540 .map((x) => x.value), 541 ).toStrictEqual(['a', 'b']); 542 }); 543 }); 544 describe('fastcat()', () => { 545 it('Can go into negative time', () => { 546 sameFirst(fastcat('a', 'b', 'c').late(1000000), fastcat('a', 'b', 'c')); 547 }); 548 }); 549 describe('slowcat()', () => { 550 it('Can be empty', () => { 551 expect(slowcat().firstCycle()).toStrictEqual([]); 552 }); 553 it('Can concatenate things slowly', () => { 554 expect( 555 slowcat('a', 'b') 556 .firstCycle() 557 .map((x) => x.value), 558 ).toStrictEqual(['a']); 559 560 expect( 561 slowcat('a', 'b') 562 ._early(1) 563 .firstCycle() 564 .map((x) => x.value), 565 ).toStrictEqual(['b']); 566 567 expect( 568 slowcat('a', slowcat('b', 'c')) 569 ._early(1) 570 .firstCycle() 571 .map((x) => x.value), 572 ).toStrictEqual(['b']); 573 574 expect( 575 slowcat('a', slowcat('b', 'c')) 576 ._early(3) 577 .firstCycle() 578 .map((x) => x.value), 579 ).toStrictEqual(['c']); 580 }); 581 it('Can cat subpatterns', () => { 582 sameFirst(slowcat('a', ['b', 'c']).fast(4), sequence('a', ['b', 'c']).fast(2)); 583 }); 584 }); 585 describe('slowcatPrime()', () => { 586 it('Can be empty', () => { 587 expect(slowcatPrime().firstCycle()).toStrictEqual([]); 588 }); 589 it('Can slowcat patterns swapping back and forth skipping the expected notes', () => { 590 expect( 591 slowcatPrime(fastcat(0, 1, 2, 3).slow(2), fastcat(4, 5, 6, 7).slow(2)) 592 .fast(4) 593 .firstCycle() 594 .map((a) => a.value), 595 ).toStrictEqual([0, 1, 6, 7, 0, 1, 6, 7]); 596 }); 597 it('Can go into negative time', () => { 598 expect( 599 slowcatPrime(fastcat(0, 1, 2, 3).slow(2), fastcat(4, 5, 6, 7).slow(2)) 600 .fast(4) 601 .late(8) 602 .firstCycle() 603 .map((a) => a.value), 604 ).toStrictEqual([0, 1, 6, 7, 0, 1, 6, 7]); 605 }); 606 }); 607 describe('arp()', () => { 608 it('It wraps around with both positive and negative indices', () => { 609 expect( 610 stack('a', 'b', 'c') 611 .arp(fastcat(-3, -2, -1, 0, 1, 2, 3, 4)) 612 .firstCycle() 613 .map((a) => a.value), 614 ).toStrictEqual(['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b']); 615 }); 616 }); 617 describe('rev()', () => { 618 it('Can reverse things', () => { 619 expect( 620 fastcat('a', 'b', 'c') 621 .rev() 622 .firstCycle() 623 .sort((a, b) => a.part.begin.sub(b.part.begin)) 624 .map((a) => a.value), 625 ).toStrictEqual(['c', 'b', 'a']); 626 }); 627 it('Does not reverse the order of cycles', () => { 628 expect(fastcat('a', 'b', 'c', 'd').slow(2).rev().fast(2).sortHapsByPart().firstCycle()).toStrictEqual( 629 fastcat('b', 'a', 'd', 'c').firstCycle(), 630 ); 631 }); 632 }); 633 describe('revv()', () => { 634 it('Does reverse the order of cycles', () => { 635 expect(fastcat('a', 'b', 'c', 'd').slow(2).revv().fast(2).sortHapsByPart().firstCycle()).toStrictEqual( 636 fastcat('d', 'c', 'b', 'a').firstCycle(), 637 ); 638 }); 639 }); 640 describe('sequence()', () => { 641 it('Can work like fastcat', () => { 642 expect(sequence(1, 2, 3).firstCycle()).toStrictEqual(fastcat(1, 2, 3).firstCycle()); 643 }); 644 }); 645 describe('palindrome()', () => { 646 it('Can create palindrome', () => { 647 expect( 648 fastcat('a', 'b', 'c') 649 .palindrome() 650 .fast(2) 651 .firstCycle() 652 .sort((a, b) => a.part.begin.sub(b.part.begin)) 653 .map((a) => a.value), 654 ).toStrictEqual(['a', 'b', 'c', 'c', 'b', 'a']); 655 }); 656 }); 657 describe('polyrhythm()', () => { 658 it('Can layer up cycles', () => { 659 expect(polyrhythm(['a', 'b'], ['c']).firstCycle()).toStrictEqual( 660 stack(fastcat(pure('a'), pure('b')), pure('c')).firstCycle(), 661 ); 662 }); 663 }); 664 describe('polymeter()', () => { 665 it('Can layer up cycles, stepwise, with lists', () => { 666 expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual( 667 stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(), 668 ); 669 }); 670 });
672 describe('firstOf()', () => { 673 it('Can apply a function every 3rd time', () => { 674 expect( 675 pure('a') 676 .firstOf(3, (x) => x._fast(2)) 677 ._fast(3) 678 .firstCycle(), 679 ).toStrictEqual(sequence(sequence('a', 'a'), 'a', 'a').firstCycle()); 680 }); 681 it('Works as a toplevel function', () => { 682 expect(firstOf(3, fast(2), pure('a'))._fast(3).firstCycle()).toStrictEqual( 683 sequence(sequence('a', 'a'), 'a', 'a').firstCycle(), 684 ); 685 }); 686 it('Works as a toplevel function, with a patterned first argument', () => { 687 expect(firstOf(pure(3), fast(2), pure('a'))._fast(3).firstCycle()).toStrictEqual( 688 sequence(sequence('a', 'a'), 'a', 'a').firstCycle(), 689 ); 690 }); 691 it('works with currying', () => { 692 expect(pure('a').firstOf(3, fast(2))._fast(3).firstCycle()).toStrictEqual( 693 sequence(sequence('a', 'a'), 'a', 'a').firstCycle(), 694 ); 695 expect(sequence(3, 4, 5).firstOf(3, add(3)).fast(5).firstCycle()).toStrictEqual( 696 sequence(6, 7, 8, 3, 4, 5, 3, 4, 5, 6, 7, 8, 3, 4, 5).firstCycle(), 697 ); 698 expect(sequence(3, 4, 5).firstOf(2, sub(1)).fast(5).firstCycle()).toStrictEqual( 699 sequence(2, 3, 4, 3, 4, 5, 2, 3, 4, 3, 4, 5, 2, 3, 4).firstCycle(), 700 ); 701 expect(sequence(3, 4, 5).firstOf(3, add(3)).firstOf(2, sub(1)).fast(2).firstCycle()).toStrictEqual( 702 sequence(5, 6, 7, 3, 4, 5).firstCycle(), 703 ); 704 }); 705 }); 706 describe('brak()', () => { 707 it('Can make something a bit breakbeaty', () => { 708 sameFirst(sequence('a', 'b').brak()._fast(2), sequence('a', 'b', fastcat(silence, 'a'), fastcat('b', silence))); 709 }); 710 }); 711 describe('timeCat()', () => { 712 it('Can concatenate patterns with different relative durations', () => { 713 expect(sequence('a', ['a', 'a']).firstCycle()).toStrictEqual( 714 timeCat([1, 'a'], [0.5, 'a'], [0.5, 'a']).firstCycle(), 715 ); 716 }); 717 }); 718 describe('struct()', () => { 719 it('Can restructure a discrete pattern', () => { 720 expect( 721 sequence('a', 'b') 722 .struct(sequence(true, true, true)) 723 .firstCycle(), 724 ).toStrictEqual([ 725 hap(ts(0, third), ts(0, third), 'a'), 726 hap(ts(third, twothirds), ts(third, 0.5), 'a'), 727 hap(ts(third, twothirds), ts(0.5, twothirds), 'b'), 728 hap(ts(twothirds, 1), ts(twothirds, 1), 'b'), 729 ]); 730 731 expect( 732 pure('a') 733 .struct(sequence(true, [true, false], true)) 734 .firstCycle(), 735 ).toStrictEqual(sequence('a', ['a', silence], 'a').firstCycle()); 736 737 expect( 738 pure('a') 739 .struct(sequence(true, [true, false], true).invert()) 740 .firstCycle(), 741 ).toStrictEqual(sequence(silence, [silence, 'a'], silence).firstCycle()); 742 743 expect( 744 pure('a') 745 .struct(sequence(true, [true, silence], true)) 746 .firstCycle(), 747 ).toStrictEqual(sequence('a', ['a', silence], 'a').firstCycle()); 748 }); 749 it('Can structure a continuous pattern', () => { 750 expect(steady('a').struct(true, [true, true]).firstCycle()).toStrictEqual(sequence('a', ['a', 'a']).firstCycle()); 751 }); 752 }); 753 describe('mask()', () => { 754 it('Can fragment a pattern', () => { 755 expect( 756 sequence('a', 'b') 757 .mask(sequence(true, true, true)) 758 .firstCycle(), 759 ).toStrictEqual([ 760 hap(ts(0, 0.5), ts(0, third), 'a'), 761 hap(ts(0, 0.5), ts(third, 0.5), 'a'), 762 hap(ts(0.5, 1), ts(0.5, twothirds), 'b'), 763 hap(ts(0.5, 1), ts(twothirds, 1), 'b'), 764 ]); 765 }); 766 it('Can mask off parts of a pattern', () => { 767 expect(sequence(['a', 'b'], 'c').mask(sequence(true, false)).firstCycle()).toStrictEqual( 768 sequence(['a', 'b'], silence).firstCycle(), 769 ); 770 771 expect(sequence('a').mask(sequence(true, false)).firstCycle()).toStrictEqual([hap(ts(0, 1), ts(0, 0.5), 'a')]); 772 }); 773 }); 774 describe('invert()', () => { 775 it('Can invert a binary pattern', () => { 776 expect(sequence(true, false, [true, false]).invert().firstCycle()).toStrictEqual( 777 sequence(false, true, [false, true]).firstCycle(), 778 ); 779 }); 780 }); 781 describe('_setContext()', () => { 782 it('Can set the hap context', () => { 783 expect( 784 pure('a') 785 .setContext([ 786 [ 787 [0, 1], 788 [1, 2], 789 ], 790 ]) 791 .firstCycle(true), 792 ).toStrictEqual([ 793 hap(ts(0, 1), ts(0, 1), 'a', [ 794 [ 795 [0, 1], 796 [1, 2], 797 ], 798 ]), 799 ]); 800 }); 801 }); 802 describe('_withContext()', () => { 803 it('Can update the hap context', () => { 804 expect( 805 pure('a') 806 .setContext([ 807 [ 808 [0, 1], 809 [1, 2], 810 ], 811 ]) 812 .withContext((c) => [ 813 ...c, 814 [ 815 [3, 4], 816 [3, 4], 817 ], 818 ]) 819 .firstCycle(true), 820 ).toStrictEqual([ 821 hap(ts(0, 1), ts(0, 1), 'a', [ 822 [ 823 [0, 1], 824 [1, 2], 825 ], 826 [ 827 [3, 4], 828 [3, 4], 829 ], 830 ]), 831 ]); 832 }); 833 }); 834 describe('apply', () => { 835 it('Can apply a function', () => { 836 expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle()); 837 }), 838 it('Can apply a pattern of functions', () => { 839 expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle()); 840 expect(sequence('a', 'b').apply(fast(2), fast(3)).firstCycle()).toStrictEqual( 841 sequence('a', 'b').fast(2, 3).firstCycle(), 842 ); 843 }); 844 }); 845 describe('layer', () => { 846 it('Can layer up multiple functions', () => { 847 expect( 848 sequence(1, 2, 3) 849 .layer(fast(2), (pat) => pat.add(3, 4)) 850 .firstCycle(), 851 ).toStrictEqual(stack(sequence(1, 2, 3).fast(2), sequence(1, 2, 3).add(3, 4)).firstCycle()); 852 }); 853 }); 854 describe('early', () => { 855 it('Can shift a hap earlier', () => { 856 expect(pure(30)._late(0.25).query(st(1, 2))).toStrictEqual([ 857 hap(ts(1 / 4, 5 / 4), ts(1, 5 / 4), 30), 858 hap(ts(5 / 4, 9 / 4), ts(5 / 4, 2), 30), 859 ]); 860 }); 861 it('Can shift a hap earlier, into negative time', () => { 862 expect(pure(30)._late(0.25).query(st(0, 1))).toStrictEqual([ 863 hap(ts(-3 / 4, 1 / 4), ts(0, 1 / 4), 30), 864 hap(ts(1 / 4, 5 / 4), ts(1 / 4, 1), 30), 865 ]); 866 }); 867 }); 868 describe('off', () => { 869 it('Can offset a transformed pattern from the original', () => { 870 expect(pure(30).off(0.25, add(2)).firstCycle()).toStrictEqual( 871 stack(pure(30), pure(30).late(0.25).add(2)).firstCycle(), 872 ); 873 }); 874 }); 875 describe('jux', () => { 876 it('Can juxtapose', () => { 877 expect(pure({ a: 1 }).jux(fast(2)).sortHapsByPart().firstCycle()).toStrictEqual( 878 stack(pure({ a: 1, pan: 0 }), pure({ a: 1, pan: 1 }).fast(2)) 879 .sortHapsByPart() 880 .firstCycle(), 881 ); 882 }); 883 }); 884 describe('juxBy', () => { 885 it('Can juxtapose by half', () => { 886 expect(pure({ a: 1 }).juxBy(0.5, fast(2)).sortHapsByPart().firstCycle()).toStrictEqual( 887 stack(pure({ a: 1, pan: 0.25 }), pure({ a: 1, pan: 0.75 }).fast(2)) 888 .sortHapsByPart() 889 .firstCycle(), 890 ); 891 }); 892 }); 893 describe('_squeezeJoin', () => { 894 it('Can squeeze', () => { 895 expect( 896 sequence('a', ['a', 'a']) 897 .fmap((a) => fastcat('b', 'c')) 898 .squeezeJoin() 899 .firstCycle(), 900 ).toStrictEqual( 901 sequence( 902 ['b', 'c'], 903 [ 904 ['b', 'c'], 905 ['b', 'c'], 906 ], 907 ).firstCycle(), 908 ); 909 }); 910 it('Squeezes to the correct cycle', () => { 911 expect( 912 pure(time.struct(true)) 913 .squeezeJoin() 914 .queryArc(3, 4) 915 .map((x) => x.value), 916 ).toStrictEqual([Fraction(3)]); 917 }); 918 }); 919 describe('ply', () => { 920 it('Can ply(3)', () => { 921 expect(sequence('a', ['b', 'c']).ply(3).firstCycle()).toStrictEqual( 922 sequence(pure('a').fast(3), [pure('b').fast(3), pure('c').fast(3)]).firstCycle(), 923 ); 924 }); 925 it('Doesnt drop haps in the 9th cycle', () => { 926 // fixed with https://codeberg.org/uzu/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a 927 expect(sequence(1, 2, 3).ply(2).early(8).firstCycle().length).toBe(6); 928 }); 929 }); 930 describe('striate', () => { 931 it('Can striate(2)', () => { 932 sameFirst( 933 sequence({ sound: 'a' }).striate(2), 934 sequence({ sound: 'a', begin: 0, end: 0.5 }, { sound: 'a', begin: 0.5, end: 1 }), 935 ); 936 }); 937 }); 938 describe('chop', () => { 939 it('Can _chop(2)', () => { 940 expect(sequence({ sound: 'a' }, { sound: 'b' })._chop(2).firstCycle()).toStrictEqual( 941 sequence( 942 { sound: 'a', begin: 0, end: 0.5 }, 943 { sound: 'a', begin: 0.5, end: 1 }, 944 { sound: 'b', begin: 0, end: 0.5 }, 945 { sound: 'b', begin: 0.5, end: 1 }, 946 ).firstCycle(), 947 ); 948 }); 949 it('Can chop(2,3)', () => { 950 expect(pure({ sound: 'a' }).fast(2).chop(2, 3).sortHapsByPart().firstCycle()).toStrictEqual( 951 sequence( 952 [ 953 { sound: 'a', begin: 0, end: 0.5 }, 954 { sound: 'a', begin: 0.5, end: 1 }, 955 ], 956 [ 957 { sound: 'a', begin: 0, end: 1 / 3 }, 958 { sound: 'a', begin: 1 / 3, end: 2 / 3 }, 959 { sound: 'a', begin: 2 / 3, end: 1 }, 960 ], 961 ) 962 .sortHapsByPart() 963 .firstCycle(), 964 ); 965 }); 966 it('Can chop chops', () => { 967 expect(pure({ s: 'bev' }).chop(2).chop(2).firstCycle()).toStrictEqual(pure({ s: 'bev' }).chop(4).firstCycle()); 968 }); 969 }); 970 describe('range', () => { 971 it('Can be patterned', () => { 972 expect(sequence(0, 0).range(sequence(0, 0.5), 1).firstCycle()).toStrictEqual(sequence(0, 0.5).firstCycle()); 973 }); 974 }); 975 describe('range2', () => { 976 it('Can change the range of a bipolar pattern', () => { 977 expect(sequence(-1, -0.5, 0, 0.5).range2(1000, 1100).firstCycle()).toStrictEqual( 978 sequence(1000, 1025, 1050, 1075).firstCycle(), 979 ); 980 }); 981 }); 982 describe('run', () => { 983 it('Can run', () => { 984 expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle()); 985 }); 986 it('Can go into negative time', () => { 987 expect(run(4).late(1).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle()); 988 }); 989 }); 990 describe('randrun', () => { 991 it('Can randrun', () => { 992 expect(randrun(4).firstCycle()).toStrictEqual(sequence(2, 1, 3, 0).firstCycle()); 993 }); 994 it('Can go into negative time', () => { 995 expect(randrun(4).late(1).firstCycle()).toStrictEqual(sequence(1, 2, 0, 3).firstCycle()); 996 }); 997 }); 998 describe('binaryN', () => { 999 it('Can make a binary pattern from a decimal', () => { 1000 expect(binaryN(55532).firstCycle()).toStrictEqual( 1001 sequence(1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0).firstCycle(), 1002 ); 1003 }); 1004 it('Can make a binary pattern from patterned inputs', () => { 1005 expect(binaryN(pure(0x1337), pure(14)).firstCycle()).toStrictEqual( 1006 sequence(0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(), 1007 ); 1008 }); 1009 }); 1010 describe('ribbon', () => { 1011 it('Can ribbon', () => { 1012 expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual( 1013 sequence(2, 3, 4, 5).firstCycle(), 1014 ); 1015 }); 1016 }); 1017 describe('linger', () => { 1018 it('Can linger on the first quarter of a cycle', () => { 1019 expect(sequence(0, 1, 2, 3, 4, 5, 6, 7).linger(0.25).firstCycle()).toStrictEqual( 1020 sequence(0, 1, 0, 1, 0, 1, 0, 1).firstCycle(), 1021 ); 1022 }); 1023 }); 1024 describe('alignments', () => { 1025 it('Can squeeze arguments', () => { 1026 expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(sequence(5, 6, 6, 7).firstCycle()); 1027 }); 1028 }); 1029 describe('defragmentHaps', () => { 1030 it('Can merge two touching haps with same whole and value', () => { 1031 expect(stack(pure('a').mask(1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(1); 1032 }); 1033 it('Doesnt merge two overlapping haps', () => { 1034 expect( 1035 stack(pure('a').mask(1, 1, 0), pure('a').mask(0, 1)) 1036 .defragmentHaps() 1037 .firstCycle().length, 1038 ).toStrictEqual(2); 1039 }); 1040 it('Doesnt merge two touching haps with different values', () => { 1041 expect(stack(pure('a').mask(1, 0), pure('b').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2); 1042 }); 1043 it('Doesnt merge two touching haps with different wholes', () => { 1044 expect(stack(sequence('a', silence), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2); 1045 }); 1046 }); 1047 describe('press', () => { 1048 it('Can syncopate events', () => { 1049 sameFirst(sequence('a', 'b', 'c', 'd').press(), sequence(silence, 'a', silence, 'b', silence, 'c', silence, 'd')); 1050 }); 1051 }); 1052 describe('hurry', () => { 1053 it('Can speed up patterns and sounds', () => { 1054 sameFirst(s(sequence('a', 'b')).hurry(2), s(sequence('a', 'b')).fast(2).speed(2)); 1055 }); 1056 }); 1057 /*describe('composable functions', () => { 1058 it('Can compose functions', () => { 1059 sameFirst(sequence(3, 4).fast(2).rev().fast(2), fast(2).rev().fast(2)(sequence(3, 4))); 1060 }); 1061 it('Can compose by method chaining operators with controls', () => { 1062 sameFirst(s('bd').apply(set.n(3).fast(2)), s('bd').set.n(3).fast(2)); 1063 }); 1064 it('Can compose by method chaining operators and alignments with controls', () => { 1065 sameFirst(s('bd').apply(set.in.n(3).fast(2)), s('bd').set.n(3).fast(2)); 1066 // sameFirst(s('bd').apply(set.squeeze.n(3).fast(2)), s('bd').set.squeeze.n(3).fast(2)); 1067 }); 1068 }); 1069 describe('weave', () => { 1070 it('Can distribute patterns along a pattern', () => { 1071 sameFirst(n(0, 1).weave(2, s('bd', silence), s(silence, 'sd')), sequence(s('bd').n(0), s('sd').n(1))); 1072 }); 1073 }); 1074 */ 1075 describe('slice', () => { 1076 it('Can slice a sample', () => { 1077 sameFirst( 1078 s('break').slice(4, sequence(0, 1, 2, 3)), 1079 sequence( 1080 { begin: 0, end: 0.25, s: 'break', _slices: 4 }, 1081 { begin: 0.25, end: 0.5, s: 'break', _slices: 4 }, 1082 { begin: 0.5, end: 0.75, s: 'break', _slices: 4 }, 1083 { begin: 0.75, end: 1, s: 'break', _slices: 4 }, 1084 ), 1085 ); 1086 }); 1087 }); 1088 describe('splice', () => { 1089 it('Can splice a sample', () => { 1090 sameFirst( 1091 s('break').splice(4, sequence(0, 1, 2, 3)), 1092 sequence( 1093 { begin: 0, end: 0.25, s: 'break', _slices: 4, unit: 'c', speed: 1 }, 1094 { begin: 0.25, end: 0.5, s: 'break', _slices: 4, unit: 'c', speed: 1 }, 1095 { begin: 0.5, end: 0.75, s: 'break', _slices: 4, unit: 'c', speed: 1 }, 1096 { begin: 0.75, end: 1, s: 'break', _slices: 4, unit: 'c', speed: 1 }, 1097 ), 1098 ); 1099 }); 1100 }); 1101 describe('chunk', () => { 1102 it('Processes each cycle of the source pattern multiple times, once for each chunk', () => { 1103 expect(sequence(0, 1, 2, 3).slow(2).chunk(2, add(10)).fast(4).firstCycleValues).toStrictEqual([ 1104 10, 1, 0, 11, 12, 3, 2, 13, 1105 ]); 1106 }); 1107 }); 1108 describe('fastChunk', () => { 1109 it('Unlike chunk, cycles of the source pattern proceed cycle-by-cycle', () => { 1110 expect(sequence(0, 1, 2, 3).slow(2).fastChunk(2, add(10)).fast(4).firstCycleValues).toStrictEqual([ 1111 10, 1, 2, 13, 10, 1, 2, 13, 1112 ]); 1113 }); 1114 }); 1115 describe('repeatCycles', () => { 1116 it('Repeats each cycle of the source pattern the given number of times', () => { 1117 expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([0, 0, 1, 1, 0, 0]); 1118 }); 1119 }); 1120 describe('inhabit', () => { 1121 it('Can pattern named patterns', () => { 1122 expect( 1123 sameFirst( 1124 sequence('a', 'b', stack('a', 'b')).inhabit({ a: sequence(1, 2), b: sequence(10, 20, 30) }), 1125 sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])), 1126 ), 1127 ); 1128 }); 1129 it('Can pattern indexed patterns', () => { 1130 expect( 1131 sameFirst( 1132 sequence('0', '1', stack('0', '1')).inhabit([sequence(1, 2), sequence(10, 20, 30)]), 1133 sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])), 1134 ), 1135 ); 1136 }); 1137 }); 1138 describe('pick', () => { 1139 it('Can pattern named patterns', () => { 1140 expect( 1141 sameFirst( 1142 sequence('a', 'b', 'a', stack('a', 'b')).pick({ a: sequence(1, 2, 3, 4), b: sequence(10, 20, 30, 40) }), 1143 sequence(1, 20, 3, stack(4, 40)), 1144 ), 1145 ); 1146 }); 1147 it('Can pattern indexed patterns', () => { 1148 expect( 1149 sameFirst( 1150 sequence(0, 1, 0, stack(0, 1)).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), 1151 sequence(1, 20, 3, stack(4, 40)), 1152 ), 1153 ); 1154 }); 1155 it('Clamps indexes', () => { 1156 expect( 1157 sameFirst(sequence(0, 1, 2, 3).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), sequence(1, 20, 30, 40)), 1158 ); 1159 }); 1160 it('Is backwards compatible', () => { 1161 expect( 1162 sameFirst( 1163 pick([sequence('a', 'b'), sequence('c', 'd')], sequence(0, 1)), 1164 pick(sequence(0, 1), [sequence('a', 'b'), sequence('c', 'd')]), 1165 ), 1166 ); 1167 }); 1168 }); 1169 describe('pickmod', () => { 1170 it('Wraps indexes', () => { 1171 expect( 1172 sameFirst( 1173 sequence(0, 1, 2, 3).pickmod([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), 1174 sequence(1, 20, 3, 40), 1175 ), 1176 ); 1177 }); 1178 }); 1179 describe('_steps', () => { 1180 it('Is correctly preserved/calculated through transformations', () => { 1181 expect(sequence(0, 1, 2, 3).linger(4)._steps).toStrictEqual(Fraction(4)); 1182 expect(sequence(0, 1, 2, 3).iter(4)._steps).toStrictEqual(Fraction(4)); 1183 expect(sequence(0, 1, 2, 3).fast(4)._steps).toStrictEqual(Fraction(4)); 1184 expect(sequence(0, 1, 2, 3).hurry(4)._steps).toStrictEqual(Fraction(4)); 1185 expect(sequence(0, 1, 2, 3).rev()._steps).toStrictEqual(Fraction(4)); 1186 expect(sequence(1).segment(10)._steps).toStrictEqual(Fraction(10)); 1187 expect(sequence(1, 0, 1).invert()._steps).toStrictEqual(Fraction(3)); 1188 expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4)._steps).toStrictEqual(Fraction(8)); 1189 expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4)._steps).toStrictEqual(Fraction(8)); 1190 expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual( 1191 Fraction(4), 1192 ); 1193 expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual( 1194 Fraction(4), 1195 ); 1196 expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4)._steps).toStrictEqual(Fraction(12)); 1197 expect( 1198 pure((x) => x + 1) 1199 .setSteps(3) 1200 .appBoth(pure(1).setSteps(2))._steps, 1201 ).toStrictEqual(Fraction(6)); 1202 expect( 1203 pure((x) => x + 1) 1204 .setSteps(undefined) 1205 .appBoth(pure(1).setSteps(2))._steps, 1206 ).toStrictEqual(Fraction(2)); 1207 expect( 1208 pure((x) => x + 1) 1209 .setSteps(3) 1210 .appBoth(pure(1).setSteps(undefined))._steps, 1211 ).toStrictEqual(Fraction(3)); 1212 expect(stack(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(6)); 1213 expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setSteps(undefined))._steps).toStrictEqual(Fraction(3)); 1214 expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4))._steps).toStrictEqual(Fraction(4)); 1215 expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3)); 1216 // maybe this should double when they are either all even or all odd 1217 expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3)); 1218 expect(fastcat(0, 1).ply(3)._steps).toStrictEqual(Fraction(6)); 1219 expect(fastcat(0, 1).setSteps(undefined).ply(3)._steps).toStrictEqual(undefined); 1220 expect(fastcat(0, 1).fast(3)._steps).toStrictEqual(Fraction(2)); 1221 expect(fastcat(0, 1).setSteps(undefined).fast(3)._steps).toStrictEqual(undefined); 1222 }); 1223 }); 1224 describe('stepcat', () => { 1225 it('can cat', () => { 1226 expect(sameFirst(stepcat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5))); 1227 expect(sameFirst(stepcat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3))); 1228 }); 1229 it('calculates undefined steps as the average', () => { 1230 expect(sameFirst(stepcat(pure(1), pure(2), pure(3).setSteps(undefined)), fastcat(1, 2, 3))); 1231 }); 1232 it('works with auto-reified values', () => { 1233 expect(sameFirst(stepcat(expand(3, 'bd'), 'rim'), stepcat(expand(3, 'bd'), pure('rim')))); 1234 }); 1235 }); 1236 describe('shrink', () => { 1237 it('can shrink', () => { 1238 expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(1), sequence(0, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4))); 1239 }); 1240 it('can shrink backwards', () => { 1241 expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(-1), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0))); 1242 }); 1243 }); 1244 describe('grow', () => { 1245 it('can grow', () => { 1246 expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(1), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4))); 1247 }); 1248 it('can grow backwards', () => { 1249 expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(-1), sequence(4, 3, 4, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4))); 1250 }); 1251 }); 1252 describe('take and drop', () => { 1253 it('can take from the left', () => { 1254 expect(sameFirst(sequence(0, 1, 2, 3, 4).take(2), sequence(0, 1))); 1255 }); 1256 it('can drop from the left', () => { 1257 expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(2), sequence(2, 3, 4))); 1258 }); 1259 it('can take from the right', () => { 1260 expect(sameFirst(sequence(0, 1, 2, 3, 4).take(-2), sequence(3, 4))); 1261 }); 1262 it('can drop from the right', () => { 1263 expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(-2), sequence(0, 1, 2))); 1264 }); 1265 it('can drop nothing', () => { 1266 expect(sameFirst(pure('a').drop(0), pure('a'))); 1267 }); 1268 it('can drop nothing, repeatedly', () => { 1269 expect(sameFirst(pure('a').drop(0, 0), fastcat('a', 'a'))); 1270 for (var i = 0; i < 100; ++i) { 1271 expect(sameFirst(pure('a').drop(...Array(i).fill(0)), fastcat(...Array(i).fill('a')))); 1272 } 1273 }); 1274 }); 1275 describe('expand', () => { 1276 it('can expand four things in half', () => { 1277 expect( 1278 sameFirst(sequence(0, 1, 2, 3).expand(1, 0.5), stepcat(sequence(0, 1, 2, 3), sequence(0, 1, 2, 3).expand(0.5))), 1279 ); 1280 }); 1281 it('can expand five things in half', () => { 1282 expect( 1283 sameFirst( 1284 sequence(0, 1, 2, 3, 4).expand(1, 0.5), 1285 stepcat(sequence(0, 1, 2, 3, 4), sequence(0, 1, 2, 3, 4).expand(0.5)), 1286 ), 1287 ); 1288 }); 1289 }); 1290 describe('stepJoin', () => { 1291 it('can join a pattern with steps of 2', () => { 1292 expect( 1293 sameFirst( 1294 sequence(pure(pure('a')), pure(pure('b').setSteps(2))).stepJoin(), 1295 stepcat(pure('a'), pure('b').setSteps(2)), 1296 ), 1297 ); 1298 }); 1299 it('can join a pattern with steps of 0.5', () => { 1300 expect( 1301 sameFirst( 1302 sequence(pure(pure('a')), pure(pure('b').setSteps(0.5))).stepJoin(), 1303 stepcat(pure('a'), pure('b').setSteps(0.5)), 1304 ), 1305 ); 1306 }); 1307 }); 1308 describe('loopAt', () => { 1309 it('maintains steps', () => { 1310 expect(s('bev').chop(8).loopAt(2)._steps).toStrictEqual(Fraction(4)); 1311 }); 1312 }); 1313 describe('bite', () => { 1314 it('works with uneven patterns', () => { 1315 sameFirst( 1316 fastcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5)) 1317 .bite(2, stepcat(pure(0), pure(1).expand(2))) 1318 .fast(5), 1319 stepcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5).expand(2)).fast(5), 1320 ); 1321 }); 1322 }); 1323 describe('unjoin', () => { 1324 it('destructures a pattern into subcycles', () => { 1325 sameFirst( 1326 fastcat('a', 'b', 'c', 'd') 1327 .unjoin(fastcat(true, fastcat(true, true))) 1328 .fmap(fast(2)) 1329 .join(), 1330 fastcat('a', 'b', 'a', 'b', 'c', 'c', 'd', 'd'), 1331 ); 1332 }); 1333 }); 1334 describe('into', () => { 1335 it('applies a function to subcycles of a pattern', () => { 1336 sameFirst( 1337 fastcat('a', 'b', 'c', 'd').into(fastcat(fastcat('true', 'true'), 'true'), fast(2)), 1338 fastcat('a', 'a', 'b', 'b', 'c', 'd', 'c', 'd'), 1339 ); 1340 }); 1341 }); 1342 describe('chunkinto', () => { 1343 it('chunks into subcycles', () => { 1344 sameFirst( 1345 fastcat('a', 'b', 'c').chunkInto(3, fast(2)).fast(3), 1346 fastcat(fastcat('a', 'a'), 'b', 'c', 'a', fastcat('b', 'b'), 'c', 'a', 'b', fastcat('c', 'c')), 1347 ); 1348 }); 1349 }); 1350 describe('chunkbackinto', () => { 1351 it('chunks into subcycles backwards', () => { 1352 sameFirst( 1353 fastcat('a', 'b', 'c').chunkBackInto(3, fast(2)).fast(3), 1354 fastcat('a', 'b', fastcat('c', 'c'), 'a', fastcat('b', 'b'), 'c', fastcat('a', 'a'), 'b', 'c'), 1355 ); 1356 }); 1357 }); 1358 describe('log', () => { 1359 it('logs to console', () => { 1360 const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {}); 1361 const pattern = pure('a').log(); 1362 const haps = pattern.queryArc(0, 1);
1369 expect(mockConsoleLog).toHaveBeenCalledWith( 1370 '%c[hap] 0/1 → 1/1: a', 1371 'background-color: black;color:white;border-radius:15px', 1372 ); 1373 mockConsoleLog.mockRestore(); 1374 }); 1375 }); 1376 describe('logValues', () => { 1377 it('logs values to console', () => { 1378 const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {}); 1379 const pattern = pure('a').note('c#').logValues(); 1380 const haps = pattern.queryArc(0, 1);