Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

notepat: volume + drive FX sliders with X/Y trackpad bindings

Adds two new rows to the FX stack (below echo/pitch/crush) wired into
the C-side master_volume + drive_mix DSP landed in ea57b7b77:

vol — green slider, 0..200% master output gain. 50% tick marks
unity so the default (0.5) sounds identical to pre-slider
builds. Unity tick rendered vertically inside the slider for
visual reference.
drive — orange slider, 0..100% tanh-saturation dry/wet. Subtle warmth
in the 10-30% range; obvious distortion past 60%.

Both rows inherit the existing pattern:
* X/Y axis toggle boxes on the right (same 10px squares as echo etc.)
* Drag on the bar to set mix directly
* Trackpad X/Y delta scaling (3× sensitivity like echo/crush) when
bound via the toggle + trackpadFX active
* Throttled setMix commits on every 3rd frame during trackpad drags
(same zipper avoidance as the other sliders)

Wave-type button row shifts from sliderH*4 down to sliderH*6 so the
two new rows fit above it. Pads stay anchored 30% from screen bottom
so they don't move. Boot sets the C-side targets to match the JS
defaults (volume=unity, drive=clean); leave() resets both.

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

+144 -16
+144 -16
fedac/native/pieces/notepat.mjs
··· 111 111 let clockSyncFrame = 0; // frame counter for periodic resync 112 112 function syncedNow() { return Date.now() + clockOffset; } 113 113 114 - // FX rows: dry/wet, echo, pitch, bitcrush. 114 + // FX rows: dry/wet, echo, pitch, bitcrush, volume, drive. 115 115 let echoMix = 0; 116 116 let bitcrushMix = 0; 117 117 let echoDragging = false; ··· 124 124 let volDragging = false; 125 125 let brtDragging = false; 126 126 127 + // Master volume (user-controlled, separate from system_volume hardware 128 + // mixer). Slider range 0..1 maps to audio gain 0..2 (0..200%), so 129 + // slider at 0.5 = unity gain (1.0×). Default 0.5 means the slider 130 + // starts at the unity tick so the device sounds identical to pre-slider 131 + // builds out of the box. 132 + let masterVolMix = 0.5; 133 + let masterVolDragging = false; 134 + 135 + // Drive (tanh soft-sat dry/wet). 0 = clean, 1 = full drive. Applied 136 + // BEFORE master volume so the slider feels like a tone control. 137 + let driveMix = 0; 138 + let driveDragging = false; 139 + 127 140 // Pitch shift — assignable to either trackpad axis 128 141 let pitchShift = 0; // -1 to +1, 0 = no shift 129 142 let lastAppliedPitch = 0; // last pitch actually sent to synths (throttle) ··· 131 144 // Trackpad FX control (\ toggles on/off) 132 145 let trackpadFX = false; 133 146 let trackpadEffectBindings = { 134 - echo: { x: true, y: false }, 135 - pitch: { x: false, y: true }, 136 - crush: { x: false, y: false }, 147 + echo: { x: true, y: false }, 148 + pitch: { x: false, y: true }, 149 + crush: { x: false, y: false }, 150 + volume: { x: false, y: false }, 151 + drive: { x: false, y: false }, 137 152 }; 138 153 139 154 function clampRange(value, min, max) { ··· 166 181 return changed; 167 182 } 168 183 184 + function setMasterVolMixValue(value, sound, commit = true) { 185 + // Slider range 0..1 maps to audio gain 0..2 so the UI stays in the 186 + // same 0..100% pattern as the other FX rows. 50% = unity, 100% = 2x. 187 + const next = clamp01(value); 188 + const changed = Math.abs(next - masterVolMix) > 0.0005; 189 + masterVolMix = next; 190 + if (commit) sound?.volume?.setMix?.(masterVolMix * 2); 191 + return changed; 192 + } 193 + 194 + function setDriveMixValue(value, sound, commit = true) { 195 + const next = clamp01(value); 196 + const changed = Math.abs(next - driveMix) > 0.0005; 197 + driveMix = next; 198 + if (commit) sound?.drive?.setMix?.(driveMix); 199 + return changed; 200 + } 201 + 169 202 function applyPitchShiftToActiveSounds(force = false) { 170 203 const ep = effectivePitchShift(); 171 204 if (!force && Math.abs(ep - lastAppliedPitch) <= 0.001) return false; ··· 204 237 if (commit) sound?.fx?.setMix?.(fxMix); 205 238 return true; 206 239 } 207 - if (rowId === "echo") return setEchoMixValue(norm, sound, commit); 208 - if (rowId === "crush") return setBitcrushMixValue(norm, sound, commit); 209 - if (rowId === "pitch") return setPitchShiftValue(norm * 2 - 1, commit); 240 + if (rowId === "echo") return setEchoMixValue(norm, sound, commit); 241 + if (rowId === "crush") return setBitcrushMixValue(norm, sound, commit); 242 + if (rowId === "pitch") return setPitchShiftValue(norm * 2 - 1, commit); 243 + if (rowId === "volume") return setMasterVolMixValue(norm, sound, commit); 244 + if (rowId === "drive") return setDriveMixValue(norm, sound, commit); 210 245 return false; 211 246 } 212 247 ··· 2080 2115 sound?.room?.setMix?.(echoMix); 2081 2116 sound?.glitch?.setMix?.(bitcrushMix); 2082 2117 sound?.fx?.setMix?.(fxMix); 2118 + sound?.volume?.setMix?.(masterVolMix * 2); // slider 0..1 → audio 0..2 2119 + sound?.drive?.setMix?.(driveMix); 2083 2120 loadUdpMidiConfig(system); 2084 2121 udpMidiNextHeartbeatFrame = 0; 2085 2122 const mic = sound?.microphone || null; ··· 2841 2878 return; 2842 2879 } 2843 2880 if (pointInRect(x, y, { x: row.sliderX, y: row.y, w: row.sliderW, h: row.h })) { 2844 - if (rowId === "fx") fxDragging = true; 2845 - else if (rowId === "echo") echoDragging = true; 2846 - else if (rowId === "pitch") pitchDragging = true; 2847 - else if (rowId === "crush") bitcrushDragging = true; 2881 + if (rowId === "fx") fxDragging = true; 2882 + else if (rowId === "echo") echoDragging = true; 2883 + else if (rowId === "pitch") pitchDragging = true; 2884 + else if (rowId === "crush") bitcrushDragging = true; 2885 + else if (rowId === "volume") masterVolDragging = true; 2886 + else if (rowId === "drive") driveDragging = true; 2848 2887 setEffectRowFromPointer(rowId, x, row, sound, true); 2849 2888 return; 2850 2889 } ··· 3021 3060 djDragLastX = x; 3022 3061 } 3023 3062 const fxRows = globalThis.__fxRows || {}; 3024 - if (fxDragging) setEffectRowFromPointer("fx", x, fxRows.fx, sound, true); 3025 - if (echoDragging) setEffectRowFromPointer("echo", x, fxRows.echo, sound, true); 3026 - if (pitchDragging) setEffectRowFromPointer("pitch", x, fxRows.pitch, sound, true); 3027 - if (bitcrushDragging) setEffectRowFromPointer("crush", x, fxRows.crush, sound, true); 3063 + if (fxDragging) setEffectRowFromPointer("fx", x, fxRows.fx, sound, true); 3064 + if (echoDragging) setEffectRowFromPointer("echo", x, fxRows.echo, sound, true); 3065 + if (pitchDragging) setEffectRowFromPointer("pitch", x, fxRows.pitch, sound, true); 3066 + if (bitcrushDragging) setEffectRowFromPointer("crush", x, fxRows.crush, sound, true); 3067 + if (masterVolDragging) setEffectRowFromPointer("volume", x, fxRows.volume, sound, true); 3068 + if (driveDragging) setEffectRowFromPointer("drive", x, fxRows.drive, sound, true); 3028 3069 if (volDragging) { 3029 3070 const vb = globalThis.__volBar; 3030 3071 if (vb) { ··· 3127 3168 if (echoDragging) echoDragging = false; 3128 3169 if (pitchDragging) pitchDragging = false; 3129 3170 if (bitcrushDragging) bitcrushDragging = false; 3171 + if (masterVolDragging) masterVolDragging = false; 3172 + if (driveDragging) driveDragging = false; 3130 3173 if (volDragging) volDragging = false; 3131 3174 if (brtDragging) brtDragging = false; 3132 3175 // Release touch-triggered note ··· 3181 3224 if (trackpadFX && trackpad) { 3182 3225 let echoDirty = false; 3183 3226 let crushDirty = false; 3227 + let volDirty = false; 3228 + let driveDirty = false; 3184 3229 if (trackpad.dx !== 0) { 3185 3230 const dxNorm = trackpad.dx / Math.max(1, w); 3186 3231 if (trackpadEffectBindings.echo.x) { ··· 3192 3237 if (trackpadEffectBindings.pitch.x) { 3193 3238 setPitchShiftValue(pitchShift + dxNorm, false); 3194 3239 } 3240 + if (trackpadEffectBindings.volume.x) { 3241 + volDirty = setMasterVolMixValue(masterVolMix + dxNorm * 3, sound, false) || volDirty; 3242 + } 3243 + if (trackpadEffectBindings.drive.x) { 3244 + driveDirty = setDriveMixValue(driveMix + dxNorm * 3, sound, false) || driveDirty; 3245 + } 3195 3246 } 3196 3247 if (trackpad.dy !== 0) { 3197 3248 const dyNorm = -trackpad.dy / Math.max(1, h); ··· 3204 3255 if (trackpadEffectBindings.pitch.y) { 3205 3256 setPitchShiftValue(pitchShift + dyNorm, false); 3206 3257 } 3258 + if (trackpadEffectBindings.volume.y) { 3259 + volDirty = setMasterVolMixValue(masterVolMix + dyNorm * 3, sound, false) || volDirty; 3260 + } 3261 + if (trackpadEffectBindings.drive.y) { 3262 + driveDirty = setDriveMixValue(driveMix + dyNorm * 3, sound, false) || driveDirty; 3263 + } 3207 3264 } 3208 3265 if (echoDirty && frame % 3 === 0) sound?.room?.setMix?.(echoMix); 3209 3266 if (crushDirty && frame % 3 === 0) sound?.glitch?.setMix?.(bitcrushMix); 3267 + if (volDirty && frame % 3 === 0) sound?.volume?.setMix?.(masterVolMix * 2); 3268 + if (driveDirty && frame % 3 === 0) sound?.drive?.setMix?.(driveMix); 3210 3269 // Apply pitch shift to active voices — throttled to every 4th frame 3211 3270 // and only when pitch actually changed 3212 3271 if (frame % 4 === 0) applyPitchShiftToActiveSounds(false); ··· 4840 4899 fxRows.crush = { y: sliderY, h: sliderH, sliderX: 0, sliderW, xBox, yBox }; 4841 4900 } 4842 4901 4902 + // Master volume slider — user gain 0..200% (50% on slider = unity). 4903 + // Sits below crush so it feels like a "last stage" like a mixing board 4904 + // master fader. A tick at 50% marks unity for visual reference. 4905 + { 4906 + const sliderY = settingsY + sliderH * 4; 4907 + const sliderW = w - axisAreaW; 4908 + const hov = hoverY >= sliderY && hoverY < sliderY + sliderH; 4909 + ink(dark ? (hov ? 40 : 25) : (hov ? 220 : 235), 4910 + dark ? (hov ? 40 : 25) : (hov ? 220 : 235), 4911 + dark ? (hov ? 45 : 28) : (hov ? 225 : 238)); 4912 + box(0, sliderY, w, sliderH, true); 4913 + const fillW = Math.floor(masterVolMix * sliderW); 4914 + if (fillW > 0) { 4915 + ink(100, 220, 150, trackpadFX ? 240 : 180); 4916 + box(0, sliderY, fillW, sliderH, true); 4917 + } 4918 + // Unity tick at the 50% position. 4919 + const unityX = Math.floor(sliderW * 0.5); 4920 + ink(dark ? 90 : 160, dark ? 110 : 180, dark ? 110 : 170, 200); 4921 + box(unityX, sliderY, 1, sliderH, true); 4922 + if (masterVolMix > 0.005) { 4923 + const knobX = Math.max(1, Math.min(sliderW - 3, Math.floor(masterVolMix * sliderW))); 4924 + ink(160, 255, 190, 220); 4925 + box(knobX - 1, sliderY, 3, sliderH, true); 4926 + } 4927 + ink(dark ? 90 : 160, dark ? 90 : 160, dark ? 100 : 170); 4928 + write("vol " + Math.round(masterVolMix * 200) + "%", 4929 + { x: 2, y: sliderY + 2, size: 1, font: "font_1" }); 4930 + const xBox = { x: sliderW, y: sliderY + 1, w: axisBoxSize, h: axisBoxSize }; 4931 + const yBox = { x: sliderW + axisBoxSize + axisGap, y: sliderY + 1, w: axisBoxSize, h: axisBoxSize }; 4932 + drawAxisToggle(xBox, "x", !!trackpadEffectBindings.volume.x, [100, 220, 150]); 4933 + drawAxisToggle(yBox, "y", !!trackpadEffectBindings.volume.y, [100, 220, 150]); 4934 + fxRows.volume = { y: sliderY, h: sliderH, sliderX: 0, sliderW, xBox, yBox }; 4935 + } 4936 + 4937 + // Drive slider — tanh soft-sat dry/wet (0 = clean, 100% = fully driven). 4938 + // Subtle warmth in the 10-30% range, obvious distortion above 60%. 4939 + { 4940 + const sliderY = settingsY + sliderH * 5; 4941 + const sliderW = w - axisAreaW; 4942 + const hov = hoverY >= sliderY && hoverY < sliderY + sliderH; 4943 + ink(dark ? (hov ? 40 : 25) : (hov ? 220 : 235), 4944 + dark ? (hov ? 40 : 25) : (hov ? 220 : 235), 4945 + dark ? (hov ? 45 : 28) : (hov ? 225 : 238)); 4946 + box(0, sliderY, w, sliderH, true); 4947 + const fillW = Math.floor(driveMix * sliderW); 4948 + if (fillW > 0) { 4949 + ink(220, 90, 70, trackpadFX ? 240 : 180); 4950 + box(0, sliderY, fillW, sliderH, true); 4951 + } 4952 + if (driveMix > 0.005) { 4953 + const knobX = Math.max(1, Math.min(sliderW - 3, Math.floor(driveMix * sliderW))); 4954 + ink(255, 160, 120, 220); 4955 + box(knobX - 1, sliderY, 3, sliderH, true); 4956 + } 4957 + ink(dark ? 90 : 160, dark ? 90 : 160, dark ? 100 : 170); 4958 + write("drive " + Math.round(driveMix * 100) + "%", 4959 + { x: 2, y: sliderY + 2, size: 1, font: "font_1" }); 4960 + const xBox = { x: sliderW, y: sliderY + 1, w: axisBoxSize, h: axisBoxSize }; 4961 + const yBox = { x: sliderW + axisBoxSize + axisGap, y: sliderY + 1, w: axisBoxSize, h: axisBoxSize }; 4962 + drawAxisToggle(xBox, "x", !!trackpadEffectBindings.drive.x, [220, 90, 70]); 4963 + drawAxisToggle(yBox, "y", !!trackpadEffectBindings.drive.y, [220, 90, 70]); 4964 + fxRows.drive = { y: sliderY, h: sliderH, sliderX: 0, sliderW, xBox, yBox }; 4965 + } 4966 + 4843 4967 globalThis.__fxRows = fxRows; 4844 - const waveRowY = settingsY + sliderH * 4; 4968 + const waveRowY = settingsY + sliderH * 6; 4845 4969 const waveRowH = 14; 4846 4970 4847 4971 // === WAVE TYPE BUTTONS (below sliders, modular GUI) === ··· 5623 5747 pitchShift = 0; 5624 5748 lastAppliedPitch = 0; 5625 5749 fxMix = 1; 5750 + masterVolMix = 0.5; // unity (slider 0..1 → audio 0..2) 5751 + driveMix = 0; 5626 5752 trackpadFX = false; 5627 5753 soundAPI?.room?.setMix?.(0); 5628 5754 soundAPI?.glitch?.setMix?.(0); 5629 5755 soundAPI?.fx?.setMix?.(1); 5756 + soundAPI?.volume?.setMix?.(1); // unity 5757 + soundAPI?.drive?.setMix?.(0); // clean 5630 5758 stopAllSounds(soundAPI, systemAPI, 0.02); 5631 5759 } 5632 5760