Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(percussion): every drum is now two-step (DOWN impact + UP release)

All 12 drums in the kit — kick, snare, clap, snap, closed hat, open
hat, ride, crash, splash, cowbell, wood block, tambourine — now fire
as a two-step DOWN/UP pair instead of a single simultaneous layer
stack. This gives every pad a natural percussive arc (impact → release)
and unifies the kit's character.

Design per drum:
- kick: beater click + body thump + sawtooth grit → sub wobble bloom
- snare: stick hit on head → wire rattle + shell ring
- clap: dark palm strike → bright release transient
- snap: finger-release click → skin slap body ring
- hat-c: stick contact tick → tight metallic sizzle
- hat-o: metallic chip strike → sustained airy shimmer
- ride: bell-like ping strike → long shimmer wash
- crash: explosive metal impact → long sustained wash
- splash: thin high metal contact → short bright wash
- cowbell: stick-on-metal tink → resonant detuned ring
- block: stick contact tick → hollow wood body
- tambo: frame strike → jingle rattle

Gap between DOWN and UP is always BPM-locked via `flam * rn(...)` so
rolls tighten at higher tempos and widen at slower ones. Each step
also gets per-hit pan offset jitter for subtle stereo movement, on
top of the existing tone/volume/duration stochastic jitter.

Kick and crash use tight gaps (0.3-0.7 flam, ~4-9 ms at 120 BPM) so
the punch+bloom still feels like a single unified hit. Ride and open
hi-hat use longer gaps for audible strike→shimmer separation.

+152 -53
+152 -53
fedac/native/pieces/notepat.mjs
··· 565 565 // Fire a drum hit from short auto-stopping synth voices. No cleanup 566 566 // needed on key-up because every voice uses a finite duration. 567 567 // 568 - // Every drum gets STOCHASTIC variation on each press so repeated hits 569 - // don't sound machine-stamped: 570 - // - tone jitter: ±3-8% of the nominal frequency 571 - // - volume jitter: ±5-10% of the nominal volume 572 - // - duration jitter: ±5% on longer tails 573 - // - for multi-burst drums (clap), the flam timing also jitters 568 + // EVERY drum in the kit is a TWO-STEP hit — DOWN (impact/strike) then 569 + // UP (release/resonance/tail). The gap between the two steps is locked 570 + // to `flam` (BPM-derived) with per-hit jitter, so hits feel unified but 571 + // stretch/shrink with tempo. This gives every pad a natural percussive 572 + // arc instead of a single simultaneous layer stack. 573 + // 574 + // Every drum also gets STOCHASTIC variation on each press so repeated 575 + // hits don't sound machine-stamped: 576 + // - tone jitter: ±3-12% of the nominal frequency 577 + // - volume jitter: ±5-12% of the nominal volume 578 + // - duration jitter: ±5-14% on longer tails 579 + // - pan offset jitter on each step for subtle stereo movement 580 + // - the DOWN→UP gap itself jitters via rn(...) 574 581 // 575 582 // Multi-burst flam timing scales with metronomeBPM so drum rolls lock 576 583 // to the current tempo: at 120 BPM each sub-beat is ~12.5 ms. ··· 590 597 const flam = (60000 / Math.max(40, Math.min(240, metronomeBPM || 120))) * 0.025; 591 598 592 599 switch (letter) { 593 - case "c": // kick — wub/wobble bass with per-hit timbre variation 594 - // 1. Beater click — short high transient 595 - sound.synth({ type: "triangle", tone: rj(1800, 0.08) * pf, duration: 0.005, volume: rj(1.0, 0.08) * v, attack: 0.0003, decay: 0.004, pan }); 596 - sound.synth({ type: "noise", tone: rj(4000, 0.12) * pf, duration: 0.004, volume: rj(0.55, 0.10) * v, attack: 0.0003, decay: 0.003, pan }); 597 - // 2. Body thump — SQUARE for mid harmonics 598 - sound.synth({ type: "square", tone: rj(180, 0.05) * pf, duration: rj(0.02, 0.10), volume: rj(1.2, 0.06) * v, attack: 0.0005, decay: 0.018, pan }); 599 - // 3. Grit layer — sawtooth for dubstep rasp 600 - sound.synth({ type: "sawtooth", tone: rj(90, 0.04) * pf, duration: rj(0.1, 0.08), volume: rj(1.0, 0.06) * v, attack: 0.001, decay: 0.095, pan }); 601 - // 4. Sub wobble — two sines ~10 Hz apart, each with per-hit pitch jitter 602 - // so the beat frequency varies slightly (sometimes 8 Hz, sometimes 12 Hz) 600 + case "c": // kick — two-step: DOWN (beater punch) + UP (sub wobble bloom) 601 + // STEP 1 — DOWN: beater click + body thump + sawtooth grit. The sharp 602 + // impact that gives the kick its attack. All front-loaded. 603 603 { 604 + const downPan = pan + rn(-0.04, 0.04); 605 + sound.synth({ type: "triangle", tone: rj(1800, 0.08) * pf, duration: 0.005, volume: rj(1.0, 0.08) * v, attack: 0.0003, decay: 0.004, pan: downPan }); 606 + sound.synth({ type: "noise", tone: rj(4000, 0.12) * pf, duration: 0.004, volume: rj(0.55, 0.10) * v, attack: 0.0003, decay: 0.003, pan: downPan }); 607 + sound.synth({ type: "square", tone: rj(180, 0.05) * pf, duration: rj(0.02, 0.10), volume: rj(1.2, 0.06) * v, attack: 0.0005, decay: 0.018, pan: downPan }); 608 + sound.synth({ type: "sawtooth", tone: rj(90, 0.04) * pf, duration: rj(0.1, 0.08), volume: rj(1.0, 0.06) * v, attack: 0.001, decay: 0.095, pan: downPan }); 609 + } 610 + // STEP 2 — UP: sub-bass wobble bloom — the release where the kick 611 + // expands outward. Two sines ~10 Hz apart for per-hit beat-freq jitter, 612 + // plus a mid-low square fill. Very tight gap (~0.4 flam) so the punch 613 + // and bloom still feel like one unified kick hit. 614 + setTimeout(() => { 615 + const upPan = pan + rn(-0.02, 0.02); 604 616 const subLo = rj(44, 0.05); // ~41.8 → 46.2 Hz 605 617 const subHi = subLo + rn(8, 12); // ~50 → 58 Hz → beat freq 8-12 Hz 606 - sound.synth({ type: "sine", tone: subLo * pf, duration: rj(0.18, 0.05), volume: rj(1.9, 0.05) * v, attack: 0.001, decay: 0.17, pan }); 607 - sound.synth({ type: "sine", tone: subHi * pf, duration: rj(0.18, 0.05), volume: rj(1.3, 0.06) * v, attack: 0.001, decay: 0.17, pan }); 608 - } 609 - // 5. Mid-low square fill 610 - sound.synth({ type: "square", tone: rj(120, 0.05) * pf, duration: rj(0.07, 0.08), volume: rj(0.85, 0.07) * v, attack: 0.002, decay: 0.065, pan }); 618 + sound.synth({ type: "sine", tone: subLo * pf, duration: rj(0.18, 0.05), volume: rj(1.9, 0.05) * v, attack: 0.001, decay: 0.17, pan: upPan }); 619 + sound.synth({ type: "sine", tone: subHi * pf, duration: rj(0.18, 0.05), volume: rj(1.3, 0.06) * v, attack: 0.001, decay: 0.17, pan: upPan }); 620 + sound.synth({ type: "square", tone: rj(120, 0.05) * pf, duration: rj(0.07, 0.08), volume: rj(0.85, 0.07) * v, attack: 0.002, decay: 0.065, pan: upPan }); 621 + }, Math.round(flam * rn(0.3, 0.6))); 611 622 break; 612 623 613 - case "d": // snare — noise + triangle body + square ring, all jittered 614 - sound.synth({ type: "noise", tone: rj(2200, 0.08) * pf, duration: rj(0.12, 0.08), volume: rj(0.55, 0.08) * v, attack: 0.001, decay: 0.11, pan }); 615 - sound.synth({ type: "triangle", tone: rj(220, 0.04) * pf, duration: rj(0.1, 0.08), volume: rj(0.4, 0.08) * v, attack: 0.001, decay: 0.09, pan }); 616 - sound.synth({ type: "square", tone: rj(180, 0.04) * pf, duration: rj(0.05, 0.10), volume: rj(0.2, 0.10) * v, attack: 0.001, decay: 0.045, pan }); 624 + case "d": // snare — two-step: DOWN (stick hit) + UP (wire rattle) 625 + // STEP 1 — DOWN: sharp stick-on-head impact. 626 + { 627 + const downPan = pan + rn(-0.04, 0.04); 628 + sound.synth({ type: "square", tone: rj(800, 0.08) * pf, duration: 0.008, volume: rj(0.70, 0.08) * v, attack: 0.0003, decay: 0.007, pan: downPan }); 629 + sound.synth({ type: "noise", tone: rj(3500, 0.10) * pf, duration: 0.006, volume: rj(0.60, 0.10) * v, attack: 0.0003, decay: 0.0055, pan: downPan }); 630 + } 631 + // STEP 2 — UP: wire rattle sustain + shell body ring. 632 + setTimeout(() => { 633 + const upPan = pan + rn(-0.04, 0.04); 634 + sound.synth({ type: "noise", tone: rj(2200, 0.08) * pf, duration: rj(0.12, 0.08), volume: rj(0.50, 0.08) * v, attack: 0.001, decay: 0.11, pan: upPan }); 635 + sound.synth({ type: "triangle", tone: rj(220, 0.04) * pf, duration: rj(0.1, 0.08), volume: rj(0.38, 0.08) * v, attack: 0.001, decay: 0.09, pan: upPan }); 636 + sound.synth({ type: "square", tone: rj(180, 0.04) * pf, duration: rj(0.05, 0.10), volume: rj(0.20, 0.10) * v, attack: 0.001, decay: 0.045, pan: upPan }); 637 + }, Math.round(flam * rn(0.3, 0.6))); 617 638 break; 618 639 619 640 case "e": // clap — two-step: DOWN (dark palm strike) then UP (bright release) ··· 636 657 }, Math.round(flam * rn(1.3, 1.7))); 637 658 break; 638 659 639 - case "f": // snap — finger snap, tone jitter for variation 640 - sound.synth({ type: "noise", tone: rj(3200, 0.12) * pf, duration: 0.015, volume: rj(0.45, 0.10) * v, attack: 0.0003, decay: 0.014, pan }); 641 - sound.synth({ type: "square", tone: rj(1800, 0.10) * pf, duration: 0.02, volume: rj(0.22, 0.10) * v, attack: 0.0005, decay: 0.018, pan }); 642 - sound.synth({ type: "triangle", tone: rj(2400, 0.10) * pf, duration: 0.025, volume: rj(0.18, 0.10) * v, attack: 0.0005, decay: 0.022, pan }); 660 + case "f": // snap — two-step: DOWN (finger click) + UP (skin slap tail) 661 + // STEP 1 — DOWN: sharp high-frequency click from compressed fingertip 662 + // release. This is the "snap" itself — very brief. 663 + { 664 + const downPan = pan + rn(-0.05, 0.05); 665 + sound.synth({ type: "noise", tone: rj(3200, 0.12) * pf, duration: 0.012, volume: rj(0.50, 0.10) * v, attack: 0.0003, decay: 0.011, pan: downPan }); 666 + sound.synth({ type: "square", tone: rj(1800, 0.10) * pf, duration: 0.008, volume: rj(0.22, 0.10) * v, attack: 0.0005, decay: 0.0075, pan: downPan }); 667 + } 668 + // STEP 2 — UP: finger slaps palm — brief hollow body ring. 669 + setTimeout(() => { 670 + const upPan = pan + rn(-0.03, 0.05); 671 + sound.synth({ type: "triangle", tone: rj(2400, 0.10) * pf, duration: 0.022, volume: rj(0.20, 0.10) * v, attack: 0.0005, decay: 0.020, pan: upPan }); 672 + sound.synth({ type: "square", tone: rj(1400, 0.10) * pf, duration: 0.015, volume: rj(0.12, 0.12) * v, attack: 0.0005, decay: 0.014, pan: upPan }); 673 + }, Math.round(flam * rn(0.4, 0.7))); 643 674 break; 644 675 645 - case "g": // closed hi-hat — brightness varies each hit 646 - sound.synth({ type: "noise", tone: rj(7000, 0.08) * pf, duration: 0.04, volume: rj(0.35, 0.10) * v, attack: 0.0005, decay: 0.035, pan }); 647 - sound.synth({ type: "noise", tone: rj(5000, 0.08) * pf, duration: 0.04, volume: rj(0.20, 0.10) * v, attack: 0.0005, decay: 0.035, pan }); 676 + case "g": // closed hi-hat — two-step: DOWN (stick tick) + UP (tight sizzle) 677 + // STEP 1 — DOWN: stick contact tick, bright transient metal spike. 678 + { 679 + const downPan = pan + rn(-0.04, 0.04); 680 + sound.synth({ type: "noise", tone: rj(9000, 0.08) * pf, duration: 0.008, volume: rj(0.40, 0.10) * v, attack: 0.0003, decay: 0.0075, pan: downPan }); 681 + } 682 + // STEP 2 — UP: brief metallic sizzle release. Much shorter than open 683 + // hat since the cymbal is clamped shut. 684 + setTimeout(() => { 685 + const upPan = pan + rn(-0.04, 0.04); 686 + sound.synth({ type: "noise", tone: rj(7000, 0.08) * pf, duration: rj(0.03, 0.15), volume: rj(0.32, 0.10) * v, attack: 0.001, decay: 0.028, pan: upPan }); 687 + sound.synth({ type: "noise", tone: rj(5000, 0.08) * pf, duration: rj(0.03, 0.15), volume: rj(0.18, 0.10) * v, attack: 0.001, decay: 0.028, pan: upPan }); 688 + }, Math.round(flam * rn(0.3, 0.5))); 648 689 break; 649 690 650 691 case "a": // open hi-hat — two-step: DOWN (chip strike) then UP (airy shimmer) ··· 663 704 }, Math.round(flam * rn(0.8, 1.2))); 664 705 break; 665 706 666 - case "b": // ride — metallic shimmer with per-hit harmonic variation 667 - sound.synth({ type: "noise", tone: rj(4200, 0.06) * pf, duration: rj(0.4, 0.08), volume: rj(0.28, 0.08) * v, attack: 0.001, decay: 0.38, pan }); 668 - sound.synth({ type: "square", tone: rj(3100, 0.06) * pf, duration: rj(0.12, 0.10), volume: rj(0.10, 0.10) * v, attack: 0.001, decay: 0.11, pan }); 669 - sound.synth({ type: "square", tone: rj(4600, 0.06) * pf, duration: rj(0.1, 0.10), volume: rj(0.08, 0.10) * v, attack: 0.001, decay: 0.09, pan }); 707 + case "b": // ride — two-step: DOWN (bell ping) + UP (shimmer wash) 708 + // STEP 1 — DOWN: bell-like ping strike — the initial mallet contact. 709 + { 710 + const downPan = pan + rn(-0.05, 0.05); 711 + sound.synth({ type: "square", tone: rj(3100, 0.06) * pf, duration: rj(0.06, 0.10), volume: rj(0.20, 0.10) * v, attack: 0.0005, decay: 0.055, pan: downPan }); 712 + sound.synth({ type: "square", tone: rj(4600, 0.06) * pf, duration: rj(0.05, 0.10), volume: rj(0.14, 0.10) * v, attack: 0.0005, decay: 0.045, pan: downPan }); 713 + } 714 + // STEP 2 — UP: sustained metallic shimmer wash — the cymbal body 715 + // resonating after the strike. Longest tail of the kit. 716 + setTimeout(() => { 717 + const upPan = pan + rn(-0.03, 0.03); 718 + sound.synth({ type: "noise", tone: rj(4200, 0.06) * pf, duration: rj(0.38, 0.08), volume: rj(0.26, 0.08) * v, attack: 0.003, decay: 0.37, pan: upPan }); 719 + sound.synth({ type: "square", tone: rj(3100, 0.06) * pf, duration: rj(0.10, 0.10), volume: rj(0.08, 0.10) * v, attack: 0.002, decay: 0.095, pan: upPan }); 720 + }, Math.round(flam * rn(0.5, 0.8))); 670 721 break; 671 722 672 - case "c#": // crash 673 - sound.synth({ type: "noise", tone: rj(3500, 0.06) * pf, duration: rj(0.55, 0.08), volume: rj(0.42, 0.08) * v, attack: 0.001, decay: 0.53, pan }); 674 - sound.synth({ type: "noise", tone: rj(6500, 0.07) * pf, duration: rj(0.45, 0.08), volume: rj(0.28, 0.08) * v, attack: 0.002, decay: 0.43, pan }); 675 - sound.synth({ type: "square", tone: rj(4200, 0.06) * pf, duration: rj(0.2, 0.10), volume: rj(0.08, 0.10) * v, attack: 0.001, decay: 0.19, pan }); 723 + case "c#": // crash — two-step: DOWN (metal impact) + UP (long wash) 724 + // STEP 1 — DOWN: explosive high-brightness metal strike. 725 + { 726 + const downPan = pan + rn(-0.06, 0.06); 727 + sound.synth({ type: "noise", tone: rj(7500, 0.08) * pf, duration: 0.015, volume: rj(0.55, 0.08) * v, attack: 0.0005, decay: 0.014, pan: downPan }); 728 + sound.synth({ type: "square", tone: rj(4200, 0.06) * pf, duration: 0.012, volume: rj(0.20, 0.10) * v, attack: 0.0005, decay: 0.011, pan: downPan }); 729 + } 730 + // STEP 2 — UP: long sustained wash. Where the energy actually lives. 731 + setTimeout(() => { 732 + const upPan = pan + rn(-0.04, 0.04); 733 + sound.synth({ type: "noise", tone: rj(3500, 0.06) * pf, duration: rj(0.55, 0.08), volume: rj(0.40, 0.08) * v, attack: 0.005, decay: 0.54, pan: upPan }); 734 + sound.synth({ type: "noise", tone: rj(6500, 0.07) * pf, duration: rj(0.45, 0.08), volume: rj(0.28, 0.08) * v, attack: 0.005, decay: 0.44, pan: upPan }); 735 + sound.synth({ type: "square", tone: rj(4200, 0.06) * pf, duration: rj(0.2, 0.10), volume: rj(0.08, 0.10) * v, attack: 0.002, decay: 0.19, pan: upPan }); 736 + }, Math.round(flam * rn(0.4, 0.7))); 676 737 break; 677 738 678 - case "d#": // splash 679 - sound.synth({ type: "noise", tone: rj(5500, 0.08) * pf, duration: rj(0.3, 0.10), volume: rj(0.38, 0.08) * v, attack: 0.001, decay: 0.29, pan }); 680 - sound.synth({ type: "noise", tone: rj(8500, 0.08) * pf, duration: rj(0.22, 0.10), volume: rj(0.25, 0.10) * v, attack: 0.001, decay: 0.21, pan }); 739 + case "d#": // splash — two-step: DOWN (thin metal tick) + UP (short wash) 740 + // STEP 1 — DOWN: thin high metal contact — the splash's sharp front. 741 + { 742 + const downPan = pan + rn(-0.05, 0.05); 743 + sound.synth({ type: "noise", tone: rj(9500, 0.10) * pf, duration: 0.010, volume: rj(0.45, 0.08) * v, attack: 0.0004, decay: 0.009, pan: downPan }); 744 + } 745 + // STEP 2 — UP: short bright wash — splash is all quick burst, no sustain. 746 + setTimeout(() => { 747 + const upPan = pan + rn(-0.03, 0.03); 748 + sound.synth({ type: "noise", tone: rj(5500, 0.08) * pf, duration: rj(0.28, 0.10), volume: rj(0.36, 0.08) * v, attack: 0.003, decay: 0.27, pan: upPan }); 749 + sound.synth({ type: "noise", tone: rj(8500, 0.08) * pf, duration: rj(0.20, 0.10), volume: rj(0.23, 0.10) * v, attack: 0.003, decay: 0.19, pan: upPan }); 750 + }, Math.round(flam * rn(0.4, 0.6))); 681 751 break; 682 752 683 - case "f#": // cowbell — detuning jitters so hits sound organic 684 - sound.synth({ type: "square", tone: rj(810, 0.04) * pf, duration: rj(0.12, 0.08), volume: rj(0.22, 0.08) * v, attack: 0.001, decay: 0.11, pan }); 685 - sound.synth({ type: "square", tone: rj(540, 0.04) * pf, duration: rj(0.15, 0.08), volume: rj(0.18, 0.08) * v, attack: 0.001, decay: 0.14, pan }); 753 + case "f#": // cowbell — two-step: DOWN (stick strike) + UP (resonant ring) 754 + // STEP 1 — DOWN: stick-on-metal strike — the "tink" attack. 755 + { 756 + const downPan = pan + rn(-0.04, 0.04); 757 + sound.synth({ type: "square", tone: rj(1800, 0.06) * pf, duration: 0.008, volume: rj(0.30, 0.08) * v, attack: 0.0005, decay: 0.0075, pan: downPan }); 758 + } 759 + // STEP 2 — UP: the signature cowbell ring — two detuned squares beat. 760 + setTimeout(() => { 761 + const upPan = pan + rn(-0.03, 0.03); 762 + sound.synth({ type: "square", tone: rj(810, 0.04) * pf, duration: rj(0.12, 0.08), volume: rj(0.22, 0.08) * v, attack: 0.002, decay: 0.115, pan: upPan }); 763 + sound.synth({ type: "square", tone: rj(540, 0.04) * pf, duration: rj(0.15, 0.08), volume: rj(0.18, 0.08) * v, attack: 0.002, decay: 0.145, pan: upPan }); 764 + }, Math.round(flam * rn(0.3, 0.5))); 686 765 break; 687 766 688 - case "g#": // wood block 689 - sound.synth({ type: "triangle", tone: rj(900, 0.06) * pf, duration: rj(0.06, 0.10), volume: rj(0.35, 0.08) * v, attack: 0.001, decay: 0.05, pan }); 690 - sound.synth({ type: "square", tone: rj(1800, 0.08) * pf, duration: rj(0.03, 0.10), volume: rj(0.14, 0.10) * v, attack: 0.0005, decay: 0.025, pan }); 767 + case "g#": // wood block — two-step: DOWN (stick tick) + UP (hollow body) 768 + // STEP 1 — DOWN: stick contact tick — the initial bright transient. 769 + { 770 + const downPan = pan + rn(-0.04, 0.04); 771 + sound.synth({ type: "square", tone: rj(2400, 0.08) * pf, duration: 0.004, volume: rj(0.22, 0.10) * v, attack: 0.0003, decay: 0.0035, pan: downPan }); 772 + } 773 + // STEP 2 — UP: hollow body ring — the wood resonance. 774 + setTimeout(() => { 775 + const upPan = pan + rn(-0.03, 0.03); 776 + sound.synth({ type: "triangle", tone: rj(900, 0.06) * pf, duration: rj(0.06, 0.10), volume: rj(0.35, 0.08) * v, attack: 0.001, decay: 0.055, pan: upPan }); 777 + sound.synth({ type: "square", tone: rj(1800, 0.08) * pf, duration: rj(0.03, 0.10), volume: rj(0.12, 0.10) * v, attack: 0.0005, decay: 0.025, pan: upPan }); 778 + }, Math.round(flam * rn(0.3, 0.5))); 691 779 break; 692 780 693 - case "a#": // tambourine — jingle varies most per-hit (it's the whole point) 694 - sound.synth({ type: "noise", tone: rj(7000, 0.10) * pf, duration: rj(0.15, 0.12), volume: rj(0.30, 0.10) * v, attack: 0.001, decay: 0.14, pan }); 695 - sound.synth({ type: "noise", tone: rj(4500, 0.10) * pf, duration: rj(0.10, 0.12), volume: rj(0.18, 0.10) * v, attack: 0.001, decay: 0.09, pan }); 696 - sound.synth({ type: "square", tone: rj(6500, 0.08) * pf, duration: 0.05, volume: rj(0.10, 0.12) * v, attack: 0.001, decay: 0.045, pan }); 781 + case "a#": // tambourine — two-step: DOWN (frame strike) + UP (jingle rattle) 782 + // STEP 1 — DOWN: hand/stick on frame — the dry initial hit. 783 + { 784 + const downPan = pan + rn(-0.05, 0.05); 785 + sound.synth({ type: "noise", tone: rj(2200, 0.10) * pf, duration: 0.010, volume: rj(0.30, 0.10) * v, attack: 0.0003, decay: 0.009, pan: downPan }); 786 + sound.synth({ type: "square", tone: rj(1500, 0.08) * pf, duration: 0.008, volume: rj(0.15, 0.12) * v, attack: 0.0005, decay: 0.0075, pan: downPan }); 787 + } 788 + // STEP 2 — UP: the jingle rattle — where the tambourine character lives. 789 + // Max per-hit jitter since real tambourines never sound the same twice. 790 + setTimeout(() => { 791 + const upPan = pan + rn(-0.05, 0.05); 792 + sound.synth({ type: "noise", tone: rj(7000, 0.12) * pf, duration: rj(0.14, 0.14), volume: rj(0.30, 0.12) * v, attack: 0.002, decay: 0.13, pan: upPan }); 793 + sound.synth({ type: "noise", tone: rj(4500, 0.12) * pf, duration: rj(0.09, 0.14), volume: rj(0.18, 0.12) * v, attack: 0.002, decay: 0.085, pan: upPan }); 794 + sound.synth({ type: "square", tone: rj(6500, 0.10) * pf, duration: 0.05, volume: rj(0.10, 0.14) * v, attack: 0.001, decay: 0.045, pan: upPan }); 795 + }, Math.round(flam * rn(0.4, 0.7))); 697 796 break; 698 797 } 699 798 }