Woofer by JustADude
===================

This VaM MVRScript adds one or more generated, synchronized movable woofers to
an Empty atom. It does not require an AptSpeaker.

Release version 1 includes the RestraintAttachments-style teal, orange, cream, and black interface,
compact section headers, centered branding, and independent remote overrides
for scale, colors, woofer count, and horn tweeter visibility.

Audio drive is selected with Off/Bass/Treble/Bass + Treble dropdowns.
Non-inheriting remotes can also override woofer and trim drive, trim emission
and intensity, woofer air, wood-grain strength, JL decal visibility, and
local Y rotation. Custom remote cones and surrounds share the same motion.

The generated stack is parented to the Empty atom's main controller. Moving,
rotating, scaling, parenting, or animating the Empty carries the entire stack.
Center X/Y/Z are local offsets from the Empty controller's centered origin.
Turning the Empty atom off immediately hides the complete generated speaker,
and turning it back on restores the Woofer Visible state.

Installation
------------

Place JustADude.Woofer.1.var in:

    <VaM>\AddonPackages\

Reload packages in VaM, then add the plugin from the VAR to an Empty atom.

Upgrading
---------

The package uses VaM's Latest script-reference policy so future updates can
resolve to the newest installed Woofer while scenes retain their stored plugin
settings and Bass Level Value Trigger configuration. Bundled texture paths are
pinned to the containing VAR because VaM does not resolve .latest paths through
its secure binary-file loader.

Keep the previous Woofer VAR installed until important scenes have been opened
and saved with the new version. This lets VaM resolve both the scene's original
script reference and the Latest update without discarding stored settings.

Usage
-----

1. Add an Empty atom.
2. Open its Plugins tab and load:
   Custom\Scripts\JustADude\Woofer\Woofer.cslist
3. Choose Number of Speakers and Speaker Spacing.
4. Move "Woofer Position" between -1 and +1. All woofers remain synchronized.
5. Enable "JL" to show the JL Audio-style dust-cap badge on every woofer.
6. Enable "Trim Emissive", choose a Trim Glow Color, and adjust Trim Glow
   Intensity to make every synchronized outer ring glow.

Multiple speaker locations
--------------------------

One Woofer plugin can render its local speaker group plus six synchronized
remote groups assigned to Empty atoms. The primary plugin Empty is available,
and the same Empty can be assigned to multiple slots. Select a REMOTE SPEAKER
button, choose its Empty Target, then use that slot's independent Selected
Remote X/Y/Z controls to stack or position it. The configured group follows its
target Empty's position and rotation.

Inherit Main Settings is enabled by default. Disable it to reveal that remote
slot's independent Scale, Cone Color, Surround Color, Dust Cap Color, Trim
Color, and Wood Color controls. Custom remotes still share the main meshes,
speaker count, animation, audio analysis, and airflow; only five lightweight
material instances are added for each customized remote.

Remote groups share the source meshes, materials, animation, audio analysis,
airflow settings, and all visual parameters. They do not run additional plugin
instances or FFT analyzers. Turn Local Speaker off when the controller Empty
should manage remote locations without displaying its own speaker. Each target
Empty's on/off and Physics Control collision settings apply to its remote group.

Inverted Dust Cap changes the cap into a smooth concave bowl. Its outer slope
is calculated from the cone slope for a clean transition, and the JL decal is
deformed with the same convex or concave surface equation.

Audio-reactive mode
-------------------

Choose an atom/audio component from "Audio Source". Use Refresh Audio Sources
after adding or removing sound atoms.

Woofer Min and Woofer Max map the selected frequency level to cone movement.
Trim Light Min and Trim Light Max map it to emissive-ring intensity.

Woofer Bass Driven / Woofer Treble Driven and Trim Bass Driven / Trim Treble
Driven are independent. If both bands are enabled for one output, the stronger
band wins. If neither is enabled, that output remains manually controlled.

Bass is fixed at 35-180 Hz and treble at 4-12 kHz. Both bands automatically
calibrate to the selected source and compensate for its AudioSource volume.

The JL badge uses a transparent Standard material, so it receives scene light
instead of remaining bright white in darkness.

Woofer airflow
--------------

Woofer Air adds a lightweight directional push effect in front of the
speaker. Air Strength ranges from 0 to 250 and defaults to 50. Air Range
controls its reach. The effect follows the final Woofer Position: positive
outward cone travel pushes
away from the cabinet. Zero and negative travel apply no force and restore
hair/clothing to their previous wind and gravity state. This works with
manual, Timeline, bass-driven, and treble-driven cone movement.

For performance, targets are scanned only during positive cone travel, cached
for three seconds, and force updates are capped at 30 Hz. Only hair and clothing
with simulation currently enabled are cached. Primary torso, neck, and shoulder
rigidbodies receive physical force.
Simulated hair and clothing receive the same gentle 0.3x matching VaM wind
vector. Like WindControl, Person hair and clothing range is measured at
chestControl and uses a broad distance-based forward field rather than the
narrower rigidbody cone. Their previous wind values are restored when they
leave the field, Woofer Air is disabled, the atom is turned off, or the plugin
is removed.

Enclosure
---------

Enclosure, Side Padding, Top Bottom Padding, Wood Grain Strength, Wood Color,
and Horn Tweeter are located in the right UI column beneath the material
colors.

The rectangular tower cabinet automatically follows woofer diameter, visual
scale, speaker count, and speaker spacing. Its depth equals the speaker
diameter plus twice the Side Padding, so no separate depth control is needed.
It uses separate wood side, top, bottom, and back panels plus a flush front
face with circular woofer cutouts and a rectangular horn cutout. The woofer and
horn assemblies sit nearly flush against the cabinet face with only a tiny
anti-z-fighting clearance. No front wood geometry exists behind the moving
drivers.

Horn Tweeter adds a rectangular flared horn above the woofer stack. The tower
automatically grows upward to contain it.

Rear Terminals adds a stereo terminal cup near the bottom of the back panel.
Terminal Panel Shape selects a square or round cup. Four raised binding nuts
provide red/black connections for the labeled left and right channels. The
terminal assembly is visual-only and adds no collision or physics cost.
The L/R markings use opaque raised geometry oriented for viewing from behind,
so they remain hidden by the cabinet when viewed from the front.

"Speaker Spacing" is the empty edge-to-edge gap between adjacent woofers. The
complete stack remains centered around Center Y.

"Surround Roundness" ranges from -0.20 to +0.20 and is internally divided by
three. The default 0.03 preserves the former 0.01 shape while giving three
times finer control per 0.01 of slider movement. Negative values invert the
surround roll.

"Woofer Position" is a registered JSONStorableFloat. Timeline, VaM triggers,
VAMAudioAnalyzer, RhythmReactor, or another plugin can therefore drive it.

Current defaults
----------------

Maximum Travel:     0.04
Woofer Min:        -0.50
Woofer Max:         1.00
Number of Speakers: 3
Speaker Spacing:    0.05
Surround Width:     0.20
Trim Light Min:     0.50
Trim Light Max:     4.00
Trim response:      Treble
Inverted Dust Cap:  On
JL Badge:           On
Enclosure:          On
Horn Tweeter:       On
Rear Terminals:     On (Round)

Notes
-----

The visible assembly is generated at runtime and recreated when the scene loads.
Only its settings are saved in the scene. Removing the plugin removes all
runtime-generated visuals and leaves the Empty atom unchanged.
The Refresh Audio Sources button scans both standard atom children and VaM's
re-parented object hierarchy, so newly loaded scene and plugin AudioSources
appear without reloading the Woofer plugin.
The enclosure has a flush wood front face with real circular driver cutouts,
preventing wood from showing through the sloped or moving woofer cones.
The front mesh includes exact cabinet-corner and shared section-boundary
vertices for a continuous wood face without coplanar overlap lines. The horn
center driver is dark gray.
The enclosure panels include collision geometry controlled by the Empty atom's
standard Physics Control collision checkbox. Driver and horn openings remain
open in the front collision mesh.
Bass Level Value Trigger opens one continuous VaM value trigger driven by the
normalized 35-180 Hz bass level. It intentionally has no start or end trigger.
Audio playback uses a lightweight dynamic update path. Static cabinet geometry,
front cutouts, collision cooking, logo geometry, and wood processing are not
rebuilt on every audio frame.
Save Woofer Preset and Load Woofer Preset store and restore the complete plugin
configuration in Saves\PluginData\JustADude\Woofer. Bass Level Value Trigger
actions are included in presets and are also serialized normally with scenes.
The enclosure uses one solver iteration, one velocity iteration, discrete
collision, and no Rigidbody interpolation. VaM's Physics Update Cap is global,
so the plugin does not override the game's cap for other atoms.
Audio analysis runs at 60 Hz with 512 FFT bins and is automatically shared by
all Woofer plugin instances that select the same AudioSource. Surround vertices
use cached trigonometry and generated normals without per-frame allocations or
Unity normal recalculation.
Destroyed AudioSources are periodically removed from the shared analyzer cache.
Front-panel cutout cells use their actual independent top, bottom, left, and
right edge distances, preventing gaps when cabinet padding makes a cell
asymmetric.
