mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 04:44:31 +00:00
refactor(h3d): extract color/tuning/splitscreen utils to src/utils.js (h3d-carve-3)
Moves 12 pure-function / compile-time-constant exports from the screen.js
IIFE into a new src/utils.js ES module:
Color utils: _h3dHexToInt, _clampByteI, _darkenInt, _lightenInt
String-count: resolveStringCount
Tuning/pitch: _NOTE_NAMES_SHARP, _BASE_OPEN_MIDI_BASS4/5, _BASE_OPEN_MIDI_GUITAR6/7/8,
_baseOpenStringMidis, _midiToPitchLabel, _openStringPitchLabelsForTuning
Splitscreen: _ssActive, _ssIsCanvasFocused
Free-identifier audit: all clean. resolveStringCount uses MAX_RENDER_STRINGS
and NSTR — copied as compile-time constants (both = 6) matching IIFE values.
_ssActive/_ssIsCanvasFocused read window.feedBackSplitscreen live per call.
Survey discrepancy declared: the 12 functions span two non-contiguous regions
in current screen.js (lines 741–882 and 1490–1503) rather than the plan's
original 1703–2193 range; function list from the plan is exact.
New test file highway_3d_utils.test.js: 19 class-killer tests.
Mutation-verified (3-char shorthand removal → RED, original → GREEN).
Panel-controls sandbox: 16 stubs added for the new imports.
Suite: node --test tests/js/highway_3d*.test.js plugins/highway_3d/tests/*.test.js
188→207/207 pass.
Plugin: 3.38.0 → 3.39.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uZ169yfoFYArXz962g7KW
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
f75e91088f
commit
c7f7c88c62
@@ -8,6 +8,7 @@
|
||||
|
||||
import { geoFretX, dZ, slideTrailEnd, camBaseDistU, camLowFretPullbackU, computeBPM, _makeGaussTex, RENDER_ORDER_LAYER_STACK, RENDER_ORDER_LAYER_INDEX, RENDER_ORDER_AT_Z_ZERO, RENDER_ORDER_FAR_CLAMP, renderOrderForLayerAtZ, _noteKey, lowerBoundT, hwyFirstRelevantFrettedTime, geoFretMid } from './src/geometry.js'; // h3d-carve-1b
|
||||
import { loadThree, T } from './src/three-loader.js'; // h3d-carve-2
|
||||
import { _h3dHexToInt, _clampByteI, _darkenInt, _lightenInt, resolveStringCount, _NOTE_NAMES_SHARP, _BASE_OPEN_MIDI_BASS4, _BASE_OPEN_MIDI_BASS5, _BASE_OPEN_MIDI_GUITAR6, _BASE_OPEN_MIDI_GUITAR7, _BASE_OPEN_MIDI_GUITAR8, _baseOpenStringMidis, _midiToPitchLabel, _openStringPitchLabelsForTuning, _ssActive, _ssIsCanvasFocused } from './src/utils.js'; // h3d-carve-3
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
@@ -738,27 +739,7 @@ import { loadThree, T } from './src/three-loader.js'; // h3d-carve-2
|
||||
// numeric hex, falling back to the default palette per missing index.
|
||||
// Mutated in place by _resolveCustomPalette so the reference stays stable.
|
||||
let _customPalette = PALETTES.default.slice();
|
||||
function _h3dHexToInt(hex) {
|
||||
if (typeof hex !== 'string') return null;
|
||||
const t = hex.trim().replace(/^#/, '');
|
||||
const full = t.length === 3 ? t[0] + t[0] + t[1] + t[1] + t[2] + t[2] : t;
|
||||
if (!/^[0-9a-fA-F]{6}$/.test(full)) return null;
|
||||
return parseInt(full, 16);
|
||||
}
|
||||
// Numeric (0xRRGGBB) darken/lighten — used to derive the gem-gradient
|
||||
// top-highlight / bottom-shade stops from a custom per-string base color
|
||||
// so the note bodies follow the custom palette (mirrors the 2D highway's
|
||||
// dim/bright derivation). factor 0..1 keeps that fraction of each channel;
|
||||
// lighten mixes t toward white.
|
||||
function _clampByteI(n) { return n < 0 ? 0 : (n > 255 ? 255 : Math.round(n)); }
|
||||
function _darkenInt(hex, factor) {
|
||||
const r = (hex >> 16) & 0xff, g = (hex >> 8) & 0xff, b = hex & 0xff;
|
||||
return (_clampByteI(r * factor) << 16) | (_clampByteI(g * factor) << 8) | _clampByteI(b * factor);
|
||||
}
|
||||
function _lightenInt(hex, t) {
|
||||
const r = (hex >> 16) & 0xff, g = (hex >> 8) & 0xff, b = hex & 0xff;
|
||||
return (_clampByteI(r + (255 - r) * t) << 16) | (_clampByteI(g + (255 - g) * t) << 8) | _clampByteI(b + (255 - b) * t);
|
||||
}
|
||||
// _h3dHexToInt, _clampByteI, _darkenInt, _lightenInt — moved to src/utils.js (h3d-carve-3).
|
||||
// Default per-string gem gradient stops [topHighlight, bottomShade] —
|
||||
// sampled from the original colour PNGs. Used verbatim for the built-in
|
||||
// palettes (and for unchanged slots of a custom palette) so the stock look
|
||||
@@ -800,86 +781,11 @@ import { loadThree, T } from './src/three-loader.js'; // h3d-carve-2
|
||||
// Extend S_COL above to support more strings.
|
||||
const MAX_RENDER_STRINGS = S_COL.length;
|
||||
|
||||
// Resolve the string count for the active arrangement. Prefer
|
||||
// bundle.stringCount (exposed by feedBack core since #93 — derived
|
||||
// from notes/chords/tuning, so it works for 5-string bass, 7- and
|
||||
// 8-string guitar, etc.). Fall back to arrangement-name detection
|
||||
// for older feedBack cores that don't emit the field. Clamp to the
|
||||
// palette size so a malformed bundle or a 12-string chart doesn't
|
||||
// index past the per-string material arrays.
|
||||
function resolveStringCount(bundle) {
|
||||
const sc = bundle && bundle.stringCount;
|
||||
if (Number.isFinite(sc) && sc >= 1) {
|
||||
return Math.min(Math.trunc(sc), MAX_RENDER_STRINGS);
|
||||
}
|
||||
return /bass/i.test(bundle?.songInfo?.arrangement || '') ? 4 : NSTR;
|
||||
}
|
||||
// resolveStringCount — moved to src/utils.js (h3d-carve-3).
|
||||
|
||||
/** Chart-format tuning entries are semitone offsets from instrument standard. */
|
||||
const _NOTE_NAMES_SHARP = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
||||
// _NOTE_NAMES_SHARP, _BASE_OPEN_MIDI_BASS4/5, _BASE_OPEN_MIDI_GUITAR6/7/8 — moved to src/utils.js (h3d-carve-3).
|
||||
|
||||
// Open-string MIDI (thick → thin), matched to RS string index 0 low.
|
||||
const _BASE_OPEN_MIDI_BASS4 = Object.freeze([28, 33, 38, 43]);
|
||||
const _BASE_OPEN_MIDI_BASS5 = Object.freeze([23, 28, 33, 38, 43]);
|
||||
const _BASE_OPEN_MIDI_GUITAR6 = Object.freeze([40, 45, 50, 55, 59, 64]);
|
||||
const _BASE_OPEN_MIDI_GUITAR7 = Object.freeze([35, 40, 45, 50, 55, 59, 64]);
|
||||
// F#/B/E standard extension — low string is a fifth below RS 7‑string low B.
|
||||
const _BASE_OPEN_MIDI_GUITAR8 = Object.freeze([28, 35, 40, 45, 50, 55, 59, 64]);
|
||||
|
||||
function _baseOpenStringMidis(sc, arrangement) {
|
||||
const isBass = /bass/i.test(arrangement || '');
|
||||
if (sc === 4 && isBass) return _BASE_OPEN_MIDI_BASS4.slice();
|
||||
if (sc === 4) return _BASE_OPEN_MIDI_GUITAR6.slice(0, 4);
|
||||
if (sc === 5 && isBass) return _BASE_OPEN_MIDI_BASS5.slice();
|
||||
if (sc === 5) return _BASE_OPEN_MIDI_GUITAR6.slice(0, 5);
|
||||
if (sc === 7) return _BASE_OPEN_MIDI_GUITAR7.slice();
|
||||
if (sc === 8) return _BASE_OPEN_MIDI_GUITAR8.slice();
|
||||
if (Number.isFinite(sc) && sc > 8) {
|
||||
const out = Array.from(_BASE_OPEN_MIDI_GUITAR8);
|
||||
let last = out[out.length - 1];
|
||||
while (out.length < sc) {
|
||||
last += 5;
|
||||
out.push(last);
|
||||
}
|
||||
return out.slice(0, sc);
|
||||
}
|
||||
const g6 = _BASE_OPEN_MIDI_GUITAR6.slice();
|
||||
if (Number.isFinite(sc) && sc < 6 && sc >= 1) return g6.slice(0, sc);
|
||||
return g6;
|
||||
}
|
||||
|
||||
function _midiToPitchLabel(midi) {
|
||||
const m = Math.round(midi);
|
||||
const octave = Math.floor(m / 12) - 1;
|
||||
const n = _NOTE_NAMES_SHARP[(m % 12 + 12) % 12];
|
||||
return n + octave;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} nEffective string count clamped like nStr / resolveStringCount
|
||||
* @param {Record<string, unknown>} songInfo WS song_info blob (subset)
|
||||
*/
|
||||
function _openStringPitchLabelsForTuning(bundle, songInfo, nEffective) {
|
||||
const n = Number.isFinite(nEffective) ? Math.min(Math.max(1, Math.trunc(nEffective)), MAX_RENDER_STRINGS) : resolveStringCount(bundle);
|
||||
// bundle first: chart-transform substitutes tuning/capo there, while
|
||||
// songInfo keeps the chart's originals by contract. A malformed
|
||||
// (non-array) bundle.tuning falls back to songInfo instead of
|
||||
// blanking the labels.
|
||||
let tuning = Array.isArray(bundle.tuning) ? bundle.tuning : (songInfo && songInfo.tuning);
|
||||
let cap = bundle.capo;
|
||||
cap = Number.isFinite(cap) ? cap : (songInfo && Number.isFinite(songInfo.capo) ? songInfo.capo : 0);
|
||||
if (!Array.isArray(tuning)) tuning = [];
|
||||
|
||||
const base = _baseOpenStringMidis(n, songInfo?.arrangement);
|
||||
const labels = [];
|
||||
for (let s = 0; s < n; s++) {
|
||||
const offRaw = tuning[s];
|
||||
const off = Number.isFinite(offRaw) ? offRaw : 0;
|
||||
const midi = (base[s] !== undefined ? base[s] : 40) + off + cap;
|
||||
labels.push(_midiToPitchLabel(midi));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
// _baseOpenStringMidis, _midiToPitchLabel, _openStringPitchLabelsForTuning — moved to src/utils.js (h3d-carve-3).
|
||||
|
||||
const STR_THICK = 0.25 * K;
|
||||
|
||||
@@ -1483,24 +1389,7 @@ import { loadThree, T } from './src/three-loader.js'; // h3d-carve-2
|
||||
// T, loadThree — moved to src/three-loader.js (h3d-carve-2).
|
||||
// T is a live-binding export; the IIFE reads the updated value after loadThree() resolves.
|
||||
|
||||
/* ======================================================================
|
||||
* Splitscreen helpers
|
||||
* ====================================================================== */
|
||||
|
||||
function _ssActive() {
|
||||
const ss = window.feedBackSplitscreen;
|
||||
if (!ss || typeof ss.isActive !== 'function' || !ss.isActive()) return false;
|
||||
return typeof ss.isCanvasFocused === 'function'
|
||||
&& typeof ss.onFocusChange === 'function'
|
||||
&& typeof ss.offFocusChange === 'function';
|
||||
}
|
||||
|
||||
function _ssIsCanvasFocused(highwayCanvas) {
|
||||
const ss = window.feedBackSplitscreen;
|
||||
if (!_ssActive()) return true;
|
||||
return !!(ss && typeof ss.isCanvasFocused === 'function' &&
|
||||
ss.isCanvasFocused(highwayCanvas));
|
||||
}
|
||||
// _ssActive, _ssIsCanvasFocused — moved to src/utils.js (h3d-carve-3).
|
||||
|
||||
// Shortcut for the wide-pane framing tuner. Opens/closes the floating panel
|
||||
// (the A/B on/off and the per-pane target live inside it now). Registered
|
||||
|
||||
Reference in New Issue
Block a user