Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

feat(percussion): drums pan by kit geometry + grid bias (not tone pitch)

Tone pan is derived from pitch (`(semitones - 12) / 15`), so C4 pans
hard left and B4 pans center. For drums this inherited the same
calculation, meaning the kick (C) always hard-panned left and the ride
(B) always near center — which is wrong for a drum kit. Drums should
pan by physical kit layout, not by the note name assigned to their pad.

New `drumPanFor(letter, gridOffset)` helper combines two factors:

1. Grid bias — left grid → -0.32, right grid → +0.32. So the two
octave grids occupy distinct stereo spaces when both have drums on.
2. Drum-type offset (DRUM_PAN_OFFSET) — kit-geometry per-drum:
kick 0.00 center
snare -0.05 barely off center
clap +0.10 slightly right
snap -0.10 slightly left
hat-c +0.25 drummer's right (closed hat)
hat-o +0.32 drummer's right (open hat)
ride +0.45 far right
crash -0.45 drummer's left
splash -0.55 far left
cowbell +0.22 right accent
block -0.18 left accent
tambo +0.30 right accent

Final pan = clamp(gridBias + drumOffset * 0.7, ±0.9). Grid side
dominates so the two grids stay distinguishable, drum offset modulates
for kit-color variety.

Wired through all 3 drum trigger sites:
- keyboard act() drum path (notepat.mjs:~1412)
- touch-tap drum pad (notepat.mjs:~1800)
- drag-rollover drum pad (notepat.mjs:~1952)

Each now computes its own `drumPan` / `touchDrumPan` / `rollDrumPan`
instead of using the melodic pan. The two-step DOWN/UP jitter inside
playPercussion still adds its own ±0.05 stereo movement on top.

+53 -9
+53 -9
fedac/native/pieces/notepat.mjs
··· 562 562 return null; 563 563 } 564 564 565 + // === DRUM PAN GEOMETRY === 566 + // Tone pan is pitch-based (low notes left, high notes right) which kind of 567 + // coincidentally matches the QWERTY position because notepat's key mapping 568 + // puts low naturals on the left. For DRUMS that model is wrong — a kick 569 + // shouldn't pan hard left just because it's on the C key. Instead drums pan 570 + // by physical kit geometry (from drummer's perspective / standard mix): 571 + // - kicks + snares near center 572 + // - hats slightly right (drummer's right hand) 573 + // - ride right, crash + splash left 574 + // - perc accents spread for color 575 + // Then the grid side biases the whole hit — left grid → left-of-center, 576 + // right grid → right-of-center — so the two grids occupy distinct stereo 577 + // spaces when both have percussion on. 578 + const DRUM_PAN_OFFSET = { 579 + c: 0.00, // kick — center 580 + d: -0.05, // snare — barely off center 581 + e: 0.10, // clap — slightly right 582 + f: -0.10, // snap — slightly left 583 + g: 0.25, // closed hi-hat — drummer's right 584 + a: 0.32, // open hi-hat — drummer's right 585 + b: 0.45, // ride — far right (drummer's right side) 586 + "c#": -0.45, // crash — drummer's left 587 + "d#": -0.55, // splash — far left 588 + "f#": 0.22, // cowbell — right 589 + "g#": -0.18, // wood block — left 590 + "a#": 0.30, // tambourine — right 591 + }; 592 + 593 + function drumPanFor(letter, gridOffset) { 594 + // gridBias: left grid skews left, right grid skews right. Keeps both grids 595 + // audible simultaneously without collapsing into a single phantom center. 596 + const gridBias = gridOffset === 1 ? 0.32 : -0.32; 597 + const drumOffset = DRUM_PAN_OFFSET[letter] ?? 0; 598 + // Drum offset at 0.7 strength so kit geometry shows but grid side dominates. 599 + return Math.max(-0.9, Math.min(0.9, gridBias + drumOffset * 0.7)); 600 + } 601 + 565 602 // Fire a drum hit from short auto-stopping synth voices. No cleanup 566 603 // needed on key-up because every voice uses a finite duration. 567 604 // ··· 1430 1467 // whole thing by the per-side master so the user can balance L/R. 1431 1468 const drumVol = (1.0 + velocity * 0.8) * master; 1432 1469 const drumPitch = Math.pow(2, effectivePitchShift()); 1470 + // Drums get their own pan (kit geometry + grid bias), not the 1471 + // pitch-based tone pan calculated above. 1472 + const drumPan = drumPanFor(letter, offset); 1433 1473 const bankSample = percussionSampleBank[drumName]; 1434 1474 if (wave === "sample" && bankSample) { 1435 1475 if (bankSample !== lastLoadedSample) { ··· 1439 1479 sound.sample.play({ 1440 1480 tone: SAMPLE_BASE_FREQ * drumPitch, 1441 1481 base: SAMPLE_BASE_FREQ, 1442 - volume: drumVol, pan, loop: false, 1482 + volume: drumVol, pan: drumPan, loop: false, 1443 1483 }); 1444 1484 } else { 1445 - playPercussion(sound, letter, drumVol, pan, drumPitch); 1485 + playPercussion(sound, letter, drumVol, drumPan, drumPitch); 1446 1486 } 1447 - recordPlayback({ kind: "drum", letter, octave: noteOctave, vel: drumVol, pan, pitch: drumPitch }); 1487 + recordPlayback({ kind: "drum", letter, octave: noteOctave, vel: drumVol, pan: drumPan, pitch: drumPitch }); 1448 1488 trail[key] = { note: letter, octave: noteOctave, brightness: velocity }; 1449 1489 return; 1450 1490 } ··· 1761 1801 const touchDrum = percussionDrumFor(hitNote.letter, hitNote.gridOffset); 1762 1802 if (touchDrum) { 1763 1803 const touchDrumPitch = Math.pow(2, effectivePitchShift()); 1804 + // Drums pan by kit geometry + grid bias, not pitch-based tone pan. 1805 + const touchDrumPan = drumPanFor(hitNote.letter, hitNote.gridOffset); 1764 1806 const bankSample = percussionSampleBank[touchDrum]; 1765 1807 if (wave === "sample" && bankSample) { 1766 1808 if (bankSample !== lastLoadedSample) { ··· 1770 1812 sound.sample.play({ 1771 1813 tone: SAMPLE_BASE_FREQ * touchDrumPitch, 1772 1814 base: SAMPLE_BASE_FREQ, 1773 - volume: 1.6, pan, loop: false, 1815 + volume: 1.6, pan: touchDrumPan, loop: false, 1774 1816 }); 1775 1817 } else { 1776 - playPercussion(sound, hitNote.letter, 1.8, pan, touchDrumPitch); 1818 + playPercussion(sound, hitNote.letter, 1.8, touchDrumPan, touchDrumPitch); 1777 1819 } 1778 - recordPlayback({ kind: "drum", letter: hitNote.letter, octave: hitNote.octave, vel: 1.8, pan, pitch: touchDrumPitch }); 1820 + recordPlayback({ kind: "drum", letter: hitNote.letter, octave: hitNote.octave, vel: 1.8, pan: touchDrumPan, pitch: touchDrumPitch }); 1779 1821 trail[hitNote.key] = { note: hitNote.letter, octave: hitNote.octave, brightness: 1.0 }; 1780 1822 touchNotes[pid] = { key: hitNote.key }; 1781 1823 return; ··· 1910 1952 const rollDrum = percussionDrumFor(hitNote.letter, hitNote.gridOffset); 1911 1953 if (rollDrum) { 1912 1954 const rollDrumPitch = Math.pow(2, effectivePitchShift()); 1955 + // Drums pan by kit geometry + grid bias, not pitch-based tone pan. 1956 + const rollDrumPan = drumPanFor(hitNote.letter, hitNote.gridOffset); 1913 1957 const bankSample = percussionSampleBank[rollDrum]; 1914 1958 if (wave === "sample" && bankSample) { 1915 1959 if (bankSample !== lastLoadedSample) { ··· 1919 1963 sound.sample.play({ 1920 1964 tone: SAMPLE_BASE_FREQ * rollDrumPitch, 1921 1965 base: SAMPLE_BASE_FREQ, 1922 - volume: 1.6, pan, loop: false, 1966 + volume: 1.6, pan: rollDrumPan, loop: false, 1923 1967 }); 1924 1968 } else { 1925 - playPercussion(sound, hitNote.letter, 1.8, pan, rollDrumPitch); 1969 + playPercussion(sound, hitNote.letter, 1.8, rollDrumPan, rollDrumPitch); 1926 1970 } 1927 - recordPlayback({ kind: "drum", letter: hitNote.letter, octave: hitNote.octave, vel: 1.8, pan, pitch: rollDrumPitch }); 1971 + recordPlayback({ kind: "drum", letter: hitNote.letter, octave: hitNote.octave, vel: 1.8, pan: rollDrumPan, pitch: rollDrumPitch }); 1928 1972 trail[hitNote.key] = { note: hitNote.letter, octave: hitNote.octave, brightness: 1.0 }; 1929 1973 touchNotes[pid] = { key: hitNote.key }; 1930 1974 return;