Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

notepat: drum param inspector (phase 1 — read-only display)

When a drum pad fires, capture the exact per-voice params sent to
sound.synth() (type, tone, attack, decay, volume, duration) and display
them in a compact panel above the drum grid. Each voice is rendered as
a colored card tinted by wave type (sine=blue, noise=grey, etc.) with
abbreviated params: "N2.5k a0 d2 v50" = 2.5kHz noise, 0ms attack,
2ms decay, 50% volume.

Inspector fades out over ~5 seconds. Phase 1 is read-only — later
phases will let you drag each param with the mouse/trackpad to dial
the sound in per-pad.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+86
+86
fedac/native/pieces/notepat.mjs
··· 65 65 let heldKeys = new Set(); // keys that are latched (won't stop on key-up) 66 66 let enterHeld = false; // true while Enter key is physically held 67 67 68 + // Per-drum voice-parameter inspector. Populated each time a drum fires 69 + // with the exact params sent to sound.synth(). Rendered above the grid 70 + // for a short window so you can see what builds up each sound. Phase 1 71 + // is read-only; later phases will let you mouse-drag each param. 72 + let lastDrumInspect = null; // { letter, name, voices: [{type,tone,dur,vol,atk,dcy,pan}], until } 73 + let drumInspectBuilder = null; 74 + 68 75 // Effective pitch shift blended by FX mix (0% fx = no pitch shift) 69 76 function effectivePitchShift() { 70 77 return pitchShift * fxMix; ··· 1063 1070 // - onRelease: optional callback that fires a standalone release 1064 1071 // burst (e.g. closed hat's subtle "lift click") 1065 1072 const addSustain = (params, bothDuration, releaseFade, releaseUpdate, onRelease) => { 1073 + if (drumInspectBuilder) drumInspectBuilder.voices.push({ 1074 + type: params?.type, tone: params?.tone, duration: bothDuration, 1075 + volume: params?.volume, attack: params?.attack, decay: params?.decay, 1076 + pan: params?.pan, kind: "sustain", 1077 + }); 1066 1078 if (isLive) { 1067 1079 const handle = sound.synth({ ...params, duration: Infinity }); 1068 1080 if (handle) { ··· 1082 1094 // One-shot hit voices still need to be tracked during live play so pitch 1083 1095 // and per-side level changes continue through the audible decay after key-up. 1084 1096 const addHit = (params) => { 1097 + if (drumInspectBuilder) drumInspectBuilder.voices.push({ 1098 + type: params?.type, tone: params?.tone, duration: params?.duration, 1099 + volume: params?.volume, attack: params?.attack, decay: params?.decay, 1100 + pan: params?.pan, kind: "hit", 1101 + }); 1085 1102 const handle = sound.synth(params); 1086 1103 if (isLive && handle) { 1087 1104 const liveEntry = { ··· 1115 1132 const HAT_FREQS = [800, 540, 522.7, 369.6]; 1116 1133 1117 1134 if (!fireDown) return; 1135 + 1136 + // Inspector: start fresh builder on each live press so we capture exactly 1137 + // the voices that fire for THIS hit. 1138 + if (isLive || phase === "both") { 1139 + drumInspectBuilder = { 1140 + letter, 1141 + name: PERCUSSION_NAMES[letter] || letter, 1142 + voices: [], 1143 + }; 1144 + } 1118 1145 1119 1146 switch (letter) { 1120 1147 // === ONE-SHOT DRUMS === ··· 1349 1376 break; 1350 1377 } 1351 1378 } 1379 + // Commit inspector snapshot if we captured voices 1380 + if (drumInspectBuilder && drumInspectBuilder.voices.length > 0) { 1381 + lastDrumInspect = { ...drumInspectBuilder, until: frame + 300 }; 1382 + } 1383 + drumInspectBuilder = null; 1352 1384 } 1353 1385 1354 1386 // Fire a WAR-kit weapon through the native WAVE_GUN DWG synth. ··· 3872 3904 3873 3905 drawGrid(LEFT_GRID, leftX, leftOctaveOffset, "left"); 3874 3906 drawGrid(RIGHT_GRID, rightX, rightOctaveOffset, "right"); 3907 + 3908 + // === DRUM INSPECTOR === 3909 + // When a drum pad fires, its per-voice synth params are captured. We 3910 + // render them just above the grid so you can see exactly what builds 3911 + // up each sound. Fades out over ~5 seconds. 3912 + if (lastDrumInspect && frame < lastDrumInspect.until) { 3913 + const dark2 = isDark(); 3914 + const remaining = lastDrumInspect.until - frame; 3915 + const alpha = Math.min(255, Math.max(40, Math.round(remaining * 1.2))); 3916 + const rowH = 10; 3917 + const rowGap = 1; 3918 + const totalH = rowH * 2 + rowGap + 2; 3919 + const insY = Math.max(0, gridTop - totalH - 2); 3920 + // Background panel 3921 + ink(0, 0, 0, Math.floor(alpha * 0.6)); 3922 + box(0, insY, w, totalH, true); 3923 + // Header: drum name + voice count 3924 + const voices = lastDrumInspect.voices; 3925 + const header = `${lastDrumInspect.name.toUpperCase()} · ${voices.length} voice${voices.length === 1 ? "" : "s"}`; 3926 + ink(dark2 ? 255 : 20, dark2 ? 220 : 40, dark2 ? 140 : 80, alpha); 3927 + write(header, { x: 3, y: insY + 1, size: 1, font: "font_1" }); 3928 + // Voice cards row 3929 + const cardY = insY + rowH + rowGap; 3930 + const cardW = Math.max(32, Math.floor((w - 6) / Math.max(1, voices.length))); 3931 + // Color per wave type 3932 + const typeColors = { 3933 + sine: [120, 200, 255], 3934 + triangle: [120, 255, 180], 3935 + square: [255, 220, 120], 3936 + sawtooth: [255, 140, 80], 3937 + noise: [220, 220, 230], 3938 + }; 3939 + for (let i = 0; i < voices.length; i++) { 3940 + const v = voices[i]; 3941 + const cx = 3 + i * cardW; 3942 + const col = typeColors[v.type] || [180, 180, 200]; 3943 + // Card background 3944 + ink(col[0], col[1], col[2], Math.floor(alpha * 0.3)); 3945 + box(cx, cardY, cardW - 1, rowH, true); 3946 + // Text: abbreviated params 3947 + const toneLabel = (v.tone !== undefined && v.tone !== null) 3948 + ? (v.tone >= 1000 ? (v.tone / 1000).toFixed(1) + "k" : Math.round(v.tone).toString()) 3949 + : "?"; 3950 + const atkMs = Math.round((v.attack || 0) * 1000); 3951 + const dcyMs = Math.round((v.decay || 0) * 1000); 3952 + const volPct = Math.round((v.volume || 0) * 100); 3953 + const typeChar = (v.type || "?").slice(0, 1).toUpperCase(); 3954 + const label = `${typeChar}${toneLabel} a${atkMs} d${dcyMs} v${volPct}`; 3955 + ink(col[0], col[1], col[2], alpha); 3956 + write(label, { x: cx + 1, y: cardY + 1, size: 1, font: "font_1" }); 3957 + } 3958 + } else if (lastDrumInspect) { 3959 + lastDrumInspect = null; 3960 + } 3875 3961 3876 3962 // === PER-SIDE MASTER VOLUME SLIDERS === 3877 3963 // Thin vertical bars flanking each grid. Drag to set the master volume