foo_vis_vumeter

foo_vis_vumeter is modern reimplementation of the analog VU meter component by DRON. It renders using DirectX 12.

Features

Verification

Run Requirements and Installation

Panels or Skins

The panels or skins files define the visualizations.

The foobar2000 component is not packaged with any panels. It supports various formats, namely foobar2000 .bin files, AIMP analog .zip skins and AIMP LED .zip skins.

Use these references to find community-made skins or create new ones using VUEditor:

Usage

The component is controlled via the context menu, accessible by right-clicking anywhere on the component window. In addition, fine tuning can be performed by using the mouse wheel.

What the mouse wheel controls is selected through the "Tuning" menu. If the changes result in bad state, use the "Options > Reset" option. The selected tuning mode value can be reset to its default using middle click.

In the context menu there are a few symbols placed next to some options. These are their meaning:

What follows are the different menu categories with a short explanation of each option.

At the very top of the context menu, the current resolution of the loaded panel is displayed as a grayed informational item. If no panel is loaded, it shows a corresponding message.

Layout:

Mode:

Levels:

Movement:

Decay:

Tuning (mouse wheel-controlled):

Options:

Configure: opens a dialog box with the tuning options.

Mix: opens a dialog box with the channel level mixer board.

Explore: opens the profile directory in Windows Explorer.

Fullscreen: toggles the component between fullscreen mode and embedded or windowed mode. Keeps the screen on and stops the screensaver from starting during fullscreen. In fullscreen mode, the mouse cursor is automatically hidden after 1 second of inactivity and reappears on mouse movement or context menu interaction.

The Fullscreen option is bolded because it is the double-click default.

The different panels/skins found during the scan of the <foobar2000 profile folder>\vumeter directory appear in lexicographical order (directories first) below the "Fullscreen" toggle.

There are some options under: Preferences > Advanced > Visualisations > VU Meter:

Tuning Mode Selection

The currently selected tuning parameter is denoted by a square symbol under the context menu's Tuning popup. The selected tuning parameter can be changed by three main methods. The first is directly through selecting the desired one in the context menu. The second is using a modifier key and the vertical mouse wheel together to cycle through the parameters in menu order. The modifier key that must be held down while the vertical mouse wheel turns is one of Esc, Right Alt or AltGr. The third is to use the horizontal mouse scroll wheel. As the tuning parameter changes, the mouse tooltip should display the active one and its current value.

Once again, the selected tuning mode value can be reset to its default using middle click and all tuning parameters will fall back to their defaults if the Options > Reset option is used.

Note: selecting a parameter simply means this is the one that will be affected by the mouse wheel and middle mouse click. The combination of all settings and tuning parameter values determines the visualization's behavior appearance.

Tuning Dialog Box

Tuning can also be done through the context menu's Configure dialog box. Notes:

Mixer Dialog Box

Channel level mixing can be customized through the context menu's Mix dialog box. Notes:

Layout Suffix

Skin filenames can include a layout suffix using the pattern {N} where N is a number from 0 to 4 corresponding to the layout modes (0 = Left+Right H, 1 = Left+Right V, 2 = Left, 3 = Right, 4 = Mono). When a skin with a layout suffix is loaded, the layout is automatically set to the specified mode. The suffix is removed from the display name in the context menu. This behavior is disabled when "Ignore Defaults" is enabled.

The component adds a dynamic menu item under foobar2000's main menu (hidden by default; hold Shift when opening to reveal). It displays the panel groups and skins, along with "Previous" and "Next" navigation commands per group.

Gestures

The press-and-tap touch gesture switches skins/panels within the same group.

Horizontal Mouse Wheel, Button 4 and Button 5

The horizontal mouse wheel and buttons 4 and 5 can be used to cycle through the selected tuning option. Be aware that buttons 4 and 5 might have other side-effects in the player such as moving to the next or previous track.

Screenshot

A screenshot can be taken by holding Ctrl and left-clicking on the component window. The screenshot is saved as a PNG file in the screenshots subfolder of the panels directory. Files are named sequentially (shot_00.png, shot_01.png, etc.).

Keyboard Shortcuts

As with the mouse buttons, be aware that any custom keyboard shortcuts set in the player can alter or prevent the component from reacting to these.

Dynamics

The needle movement can be approximated using the following formula: display = old +/- (e ^ (new - old) - 1) * rise/decay. The rise or decay factor limits how far the needle can move per frame or iteration of the calculation.

The needle position, frame number in foobar2000 .bin panels or angle in AIMP analog .zip skins, is calculated using the point-slope equation referenced to the zero dB frame or angle. For the AIMP LED .zip skins, the "light" fill is calculated by interpolating between the points in the dbs parameter's array using Catmull-Rom splines.

Needle Behavior Derivation Methodology

Used a circuit of a stereo VU meter, tracing the PCB and translating all the component values of the driving circuit into a SPICE netlist. The netlist was simulated to get the step/transient and impulse responses of the "system." Since the VU meter face plate is logarithmic, the circuit uses analog differentiator, integrator and summer circuits. To validate the netlist simulation correctness, various points of the working circuit were probed with an oscilloscope.

With the characteristics of the driving circuit in hand, turned attention to the needle. The needle can be modeled as a mass-spring-damper system. So, setting up the ODE for the system (with some minor guesses) provides the behavior characteristics of the needle. This is a common second-order system with known roots.

The needle was set up underdamped and simulated/"animated" as a PID controller (see this series for additional insights). The PID coefficients were derived from the transient and impulse response targets of the SPICE simulation as those come from the voltage levels (closest to the visualization audio levels provided by foobar2000). The actual PID coefficients are simplified into higher level abstractions that are called "rise" and "decay"--where the larger numbers means faster. And also "jitter" which is a control on the minimum increment to the error accumulator.

This methodology only provides an approximate model of the analog behavior of the needle in a discrete simulation. Although the derivation was detailed, the needle movement of this component is not and will never be an accurate duplicate to its real-world analog. However, using the tuning knobs provided along time and experimentation it should be possible to create a pleasing ballistic needle movement.

.bin File Specification

Panel Specification
===================

Reverse Engineer: Jimmy Cassis
Date: 2024-10-01

This specification defines the format of the BIN file.

Panel File Format
-----------------

The files in this format use ".bin" extension.

The panel file begins with a header.

The panel file format layout:

Offset     Size    Description

  0          2     Bitmap width (16-bit unsigned integer, little-endian), between 2 and 4096
  2          2     Bitmap height (16-bit unsigned integer, little-endian), between 2 and 4096
  4          2     Frame count (16-bit unsigned integer, little-endian), between 2 and 1024
  6          2     Zero dB frame (16-bit unsigned integer, little-endian), between 0 and 1024
  8          n     Needle delta offset list (array of 32-bit unsigned integer offsets, little-endian), `n` is "frames * 4" and the first entry must be greater than or equal to 32; note that some offsets might be repeated due to the "knots" interpolation.
  8+n        p     Background image bitmap pixel array (BI_RGB format [B8G8R8A8 UNORM], 32 bits per pixel, little-endian), `p` is "width * height * 4", format below
  8+n+p      l     Optional, lamp delta offset list (array of 32-bit unsigned integer offsets, little-endian), `l` is "frames * 4"
  8+n+p+l    d     Needle delta arrays (variable size, offsets are big-endian, deltas are BI_RGB and apply per channel), `d` is variable, format below
  8+n+p+l+d  a     Optional, lamp delta arrays (variable size, offsets are big-endian, deltas are BI_RGB and apply per channel), `a` is variable, format below

The total size of the file is: 8+n+p+l+d+a
The total size of the file must be greater than: 8+n+p

The bitmap pixel array layout is as follows (address _decreases_ from left-to-right) and arranged in top-down order:

                     +-------------------------+-------------------------+-------------------------+-------------------------+
Sample Length:       |            8            |            8            |            8            |            8            |
Channel Membership:  |          Alpha          |           Red           |          Green          |           Blue          |
Pixel Bits:          |  A  A  A  A  a  a  a  a |  R  R  R  R  r  r  r  r |  G  G  G  G  g  g  g  g |  B  B  B  B  b  b  b  b |
Bit Number:          | 31 30 29 28 27 26 25 24 | 23 22 21 20 19 18 17 16 | 15 14 13 12 11 10  9  8 |  7  6  5  4  3  2  1  0 |
                     +-------------------------+-------------------------+-------------------------+-------------------------+
                      ^                         ^
                      |                         |
                      |                         + less significant byte
                      + more significant byte

The needle and lamp delta arrays use the following format:

- For every frame there is a 2-byte big-endian offset while the most significant bit (MSB) of the even byte is unset.
- If the MSB for the even byte is set, it indicates a count (excluding the MSB) of the number of pixel channels to override starting at the offset in the pixel array indicated by the sum of all the previous 2-byte offsets.
  * Note that the offset might not necessarily start or end at a pixel boundary.
- Repeat, calculating a new offset from the end of the final overriden pixel until the even byte MSB is set and the count is zero (0x80).

Worked example for the needle delta array (address _increases_ from left-to-right):

             +-----------------------------------+-----------------------+-----+-----------------------------------------------------------+-----+---------...---+
Offset Sum:  |             174272                |           7           | 1489|                           19                              | 1481|     23  ...   |
Offset:      |32768+32768+32768+32768+32768+10438| 7| 0| 1| 2| 3| 4| 5| 6| 1488|19| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|17|18| 1480|23| 0| 1|...| 0|
Bytes:       |7F FF 7F FF 7F FF 7F FF 7F FF 28 BF 87 8A 8B 87 00 E0 E2 DE 05 D0 93 B1 B2 AE 00 69 6A 67 00 6B 6D 68 00 B8 B9 B6 00 E0 E2 DF 05 C8 97 E6 EE ... 80|
Pixel Bits:  |^^ **                              |   Bb Gg Rr Aa Bb Gg Rr|     |   Bb Gg Rr Aa Bb Gg Rr Aa Bb Gg Rr Aa Bb Gg Rr Aa Bb Gg Rr|     |   Bb Gg ...End|
             +-|--|------------------------------+-----------------------+-----+-----------------------------------------------------------+-----+---------...---+
               |  |
               |  + more significant byte
               + less significant byte

Notes
-----

When drawing, ignore the alpha channel.

Extensions
----------

1. Separate Left and Right Panels

  By default, the panel file represents both the left and the right panels.
  If the file format is repeated at the end of the delta arrays, it is assumed that the first format corresponds to the left panel and the second format corresponds to the right panel.
  The file formats can be concatenated uncompressed into one file. Alternatively, individually compressed files can be concatenated (keep the same compression scheme for both).
  Another method to provide separate left and right panels is to provide a pair of files where the left panel file ends in `1.bin` and the right panel file in `2.bin`.

2. Transparency (Alpha Channel)

  By default, the alpha channel is ignored.
  If the alpha channel is not to be ignored (BI_BITFIELDS format [B8G8R8A8 UNORM], 32 bits per pixel, little-endian), set bit 7 (the most significant bit) of the upper byte of the zero dB frame in the header.

3. Bottom-up Background Bitmap Pixel Array.

  By default, the bitmap pixel array is assumed to be in top-down order.
  If the bitmap pixel array is arranged in bottom-up order instead of top-down order, set bit 6 of the upper byte of the zero dB frame in the header.

Additional .bin Notes

Warning: Identifying LZMA-compressed heuristic is limited; especially if the skin was compressed outside of VUEditor. Therefore it is possible to confuse an uncompressed .bin file of any width less than 225 with a LZMA-uncompressed one. Using a bzip2-compressed file mitigates this issue since this scheme starts with an easily identifiable magic number.

AIMP Analog .zip Specification

Note 1: MobilityNegative, MobilityPositive, and orientation parameters are currently unused.

Note 2: The dbs parameter array is optional and only used in skins that include an LED component in addition to the needle. As noted previously, the LED image is {l_,r_,}3.png.

Note 3: Missing parameters are assumed to be 0 or false.

AIMP LED .zip Specification (LVU)

foobar2000 .rar Specification

Note 1: fall and rise parameters are currently unused.

Note 2: The peakLED parameter being true sets Levels to _Mixed as soon as the panel is loaded. The singleMeter and isVertical parameters combine to set the Layout mode. The bgColour parameter sets the background color, overriding the edge color detection. The curveAdj parameter flattens the logarithmic curve by affecting the quadratic parameter.

Note 3: Missing parameters are assumed to be 0 or false.

Standalone .ini File

COM Interface Specification

The component exposes COM interfaces for automation. The IVUMeter and IVUMeterWindow COM interfaces for managing and interacting with the foobar2000 VU Meter component. In foobar2000, these interfaces are mainly accessed through JavaScript.

Functions

com_get_interface

Retrieves a IVUMeter interface instance.

extern "C" __declspec(dllimport) HRESULT __cdecl com_get_interface(IVUMeter** pp);

Enums

LayoutEnum

Defines the layout options for the VU Meter window.

Value Name Description
0 BothH Both channels, horizontal
1 BothV Both channels, vertical
2 Left Left channel only
3 Right Right channel only
4 Mono Mono layout

Interfaces

IVUMeter

Provides control and status for the VU Meter component.

IID: {9EBE6FDE-502B-4571-922F-5D5942A53638}

Properties
Property Returns (get) / Accepts (put) Description
ComponentVersion UINT; get Gets component's version number.
ComponentVersionText BSTR; get Gets component's version as text.
LeftLevel FLOAT; get Gets left channel RMS level.
RightLevel FLOAT; get Gets right channel RMS level.
LeftPeak FLOAT; get Gets left channel peak level.
RightPeak FLOAT; get Gets right channel peak level.
Offset DOUBLE; get, put Gets or sets gain offset value.
UpdatePeriod DOUBLE; get Gets update window period.
DownmixChannels BOOL; get, put Gets or sets downmix state.
RoundCorners FLOAT; get, put Gets or sets corner rounding.
CubicInterpolation BOOL; get, put Gets or sets cubic interpolation state.
DisableTuning BOOL; get, put Gets or sets tuning disable state.
DisallowFullscreen BOOL; get, put Gets or sets fullscreen restriction state.
MultiMonFullscreen BOOL; get, put Gets or sets multi-monitor fullscreen state.
RemoveBackground BOOL; get, put Gets or sets background removal state.
EnableTransparency BOOL; get, put Gets or sets transparency state.
IgnoreDefaults BOOL; get, put Gets or sets "ignore defaults" state.
Freeze BOOL; get, put Gets or sets freeze state.
ShowCounter BOOL; get, put Gets or sets FPS counter visibility.
ScreensaverMode BOOL; get, put Gets or sets screensaver mode state.
VSync BOOL; get, put Gets or sets (no effect) VSync state.
WindowID UINT; get Gets current active window ID. Returns -1 if there is no active window.
WindowCount UINT; get Gets total window count.
Functions
Function Returns Description
Print(BSTR* Text) None Prints text to the foobar2000 console.
Rescan() None Rescans vumeter folder.
Defaults() None Restores defaults.
CreateWindow(UINT ID) IVUMeterWindow** Creates VUMeterWindow interface for the window using ID.
RegisterWindow(UINT ID) None Sets window using ID as active.
RegisterRect(UINT ID, INT Left, INT Top, INT Width, INT Height) None Registers a rectangle region -- no effect.

/----------------------------------------------------------------------------------------/

IVUMeterWindow

Represents a VU Meter window instance with properties for visibility, layout, and skin/panel configuration.

IID: {482C5F92-2D21-4F6F-9857-87C1D2C04344}

Properties
Method Returns (get) / Accepts (put) Description
Visible BOOL; get, put Gets or sets (no effect) window visibility state.
Enabled BOOL; get, put Gets or sets (no effect) window enabled state.
Layout LayoutEnum; get, put Gets or sets current layout.
LockAspectRatio BOOL; get, put Gets or sets aspect ratio lock state.
SkinName BSTR; get, put Gets current skin name or changes skin.
GroupName BSTR; get, put Gets current group name or changes skin to first one in group.
GroupCount UINT; get Gets number of groups (directories).
SkinCount UINT; get Gets number of skins in group of currently-selected skin.
Functions
Function Returns Description
SetBounds(int Left, int Top, int Width, int Height) None Sets window position and size -- no effect.
SetConfig(BSTR GroupName, BSTR SkinName, LayoutEnum Layout) None Changes skin and layout.
LoadSkin(BSTR GroupName, BSTR SkinName) None Changes skin.
LoadPrevSkin(BSTR GroupName) None Loads previous skin in group (does not wrap around).
LoadNextSkin(BSTR GroupName) None Loads next skin in group (does not wrap around).
GroupSize(UINT Index) UINT Gets size of a group by index.
Groups(UINT Index) BSTR Gets group name by index.
Skins(UINT Index) BSTR Gets skin name by index in group.
Skin(UINT Group, UINT Index) BSTR Gets skin name by group index and skin index.