Initial audio support

This commit is contained in:
2026-05-04 14:32:29 +10:00
parent 44316b29c2
commit f836c53d10
17 changed files with 977 additions and 10 deletions

View File

@@ -0,0 +1,76 @@
{
"id": "audio-vu-meter",
"name": "Audio VU Meter",
"description": "Draws stereo audio level meters from the runtime audio analysis data.",
"category": "Utility",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "meterPosition",
"label": "Position",
"type": "vec2",
"default": [0.08, 0.82],
"min": [0.0, 0.0],
"max": [1.0, 1.0],
"step": [0.01, 0.01]
},
{
"id": "meterScale",
"label": "Scale",
"type": "float",
"default": 0.35,
"min": 0.1,
"max": 1.0,
"step": 0.01
},
{
"id": "meterOpacity",
"label": "Opacity",
"type": "float",
"default": 0.9,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "noiseGate",
"label": "Noise Gate",
"type": "float",
"default": 0.03,
"min": 0.0,
"max": 0.5,
"step": 0.01
},
{
"id": "meterColor",
"label": "Meter Color",
"type": "color",
"default": [0.2, 1.0, 0.55, 1.0]
},
{
"id": "peakColor",
"label": "Peak Color",
"type": "color",
"default": [1.0, 0.85, 0.2, 1.0]
},
{
"id": "backgroundOpacity",
"label": "Background",
"type": "float",
"default": 0.45,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "orientation",
"label": "Orientation",
"type": "enum",
"default": "horizontal",
"options": [
{ "value": "horizontal", "label": "Horizontal" },
{ "value": "vertical", "label": "Vertical" }
]
}
]
}

View File

@@ -0,0 +1,59 @@
float rectMask(float2 uv, float2 minUv, float2 maxUv)
{
float2 insideMin = step(minUv, uv);
float2 insideMax = step(uv, maxUv);
return insideMin.x * insideMin.y * insideMax.x * insideMax.y;
}
float denoiseLevel(float value)
{
float gate = saturate(noiseGate);
float clean = saturate((value - gate) / max(1.0 - gate, 0.001));
return smoothstep(0.0, 1.0, clean);
}
float4 shadeVideo(ShaderContext context)
{
float4 color = context.sourceColor;
float2 size = orientation == 0 ? float2(meterScale, meterScale * 0.18) : float2(meterScale * 0.18, meterScale);
float2 minUv = clamp(meterPosition, 0.0, 1.0 - size);
float2 local = (context.uv - minUv) / max(size, float2(0.001));
float inside = rectMask(local, float2(0.0), float2(1.0));
if (inside <= 0.0)
return color;
float3 bg = lerp(color.rgb, float3(0.0), saturate(backgroundOpacity));
float leftLevel = denoiseLevel(context.audioRms.x * 2.4);
float rightLevel = denoiseLevel(context.audioRms.y * 2.4);
float leftPeak = denoiseLevel(context.audioPeak.x);
float rightPeak = denoiseLevel(context.audioPeak.y);
float bar = 0.0;
float peak = 0.0;
if (orientation == 0)
{
float leftRow = rectMask(local, float2(0.04, 0.58), float2(0.96, 0.86));
float rightRow = rectMask(local, float2(0.04, 0.14), float2(0.96, 0.42));
float leftFill = rectMask(local, float2(0.04, 0.58), float2(0.04 + 0.92 * leftLevel, 0.86));
float rightFill = rectMask(local, float2(0.04, 0.14), float2(0.04 + 0.92 * rightLevel, 0.42));
float leftPeakLine = rectMask(local, float2(0.04 + 0.92 * leftPeak - 0.006, 0.55), float2(0.04 + 0.92 * leftPeak + 0.006, 0.89));
float rightPeakLine = rectMask(local, float2(0.04 + 0.92 * rightPeak - 0.006, 0.11), float2(0.04 + 0.92 * rightPeak + 0.006, 0.45));
bar = max(leftFill, rightFill);
peak = max(leftPeakLine * leftRow, rightPeakLine * rightRow);
}
else
{
float leftColumn = rectMask(local, float2(0.14, 0.04), float2(0.42, 0.96));
float rightColumn = rectMask(local, float2(0.58, 0.04), float2(0.86, 0.96));
float leftFill = rectMask(local, float2(0.14, 0.04), float2(0.42, 0.04 + 0.92 * leftLevel));
float rightFill = rectMask(local, float2(0.58, 0.04), float2(0.86, 0.04 + 0.92 * rightLevel));
float leftPeakLine = rectMask(local, float2(0.11, 0.04 + 0.92 * leftPeak - 0.006), float2(0.45, 0.04 + 0.92 * leftPeak + 0.006));
float rightPeakLine = rectMask(local, float2(0.55, 0.04 + 0.92 * rightPeak - 0.006), float2(0.89, 0.04 + 0.92 * rightPeak + 0.006));
bar = max(leftFill * leftColumn, rightFill * rightColumn);
peak = max(leftPeakLine, rightPeakLine);
}
float3 metered = lerp(bg, meterColor.rgb, bar * saturate(meterOpacity) * meterColor.a);
metered = lerp(metered, peakColor.rgb, peak * saturate(meterOpacity) * peakColor.a);
return float4(metered, color.a);
}