Skip to content

Commit f36159d

Browse files
authored
Add audio_peak and audio_magnitude support. (#85)
1 parent 304434a commit f36159d

3 files changed

Lines changed: 187 additions & 0 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ handle these variables being missing, but the shader may malfunction.)
142142
this to convert the UV coordinates of the pixel being processed to the coordinates of that texel in the source
143143
texture, or otherwise scale UV coordinate distances into texel distances.
144144
* **`prevous_output`** (`texture2d`)—The previous output of the filter (2.5.0)
145+
* **`audio_peak`** (`float`)—The instantaneous maximum audio level (peak) from the selected audio source, normalized to 0.0-1.0.
146+
More reactive to sudden sounds like drums.
147+
* **`audio_magnitude`** (`float`)—The RMS (Root Mean Square) audio level from the selected audio source, normalized to 0.0-1.0.
148+
Smoother representation of sustained audio levels.
145149

146150
### Optional Preprocessing Macros
147151

@@ -165,6 +169,7 @@ I recommend *.shader* as they do not require `Use Effect File (.effect)` as pixe
165169
| animated_texture.effect | Animates a texture with polar sizing and color options | |
166170
| alpha-gaming-bent-camera.shader | | ![image](https://github.com/exeldro/obs-shaderfilter/assets/5457024/5fb6fec8-fc1b-46eb-96aa-17ce37a7ca20) |
167171
| ascii.shader | a little example of ascii art | ![image](https://github.com/exeldro/obs-shaderfilter/assets/5457024/682ad2d3-d32a-464e-a3af-0791ba0fc829) |
172+
| audio.shader | An example of a shader that reacts to real audio monitoring.
168173
| background_removal.effect | simple implementation of background removal. Optional color space corrections | |
169174
| blink.shader | A shader that fades the opacity of the output in and out over time, with a configurable speed multiplier. Demonstrates the user of the `elapsed_time` parameter. | |
170175
| bloom.shader | simple shaders to add bloom effects | ![image](https://github.com/exeldro/obs-shaderfilter/assets/5457024/567e5dc4-ec20-42fa-a344-2be1e6516b01) |

data/examples/audio.shader

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Audio shader example showing the difference between audio_peak and audio_magnitude.
2+
// Left half uses audio_peak (red), right half uses audio_magnitude (blue).
3+
uniform float audio_peak;
4+
uniform float audio_magnitude;
5+
6+
uniform float intensity <
7+
string label = "Audio intensity";
8+
string widget_type = "slider";
9+
float minimum = 0.1;
10+
float maximum = 3.0;
11+
float step = 0.1;
12+
> = 1.0;
13+
14+
float4 mainImage(VertData v_in) : TARGET {
15+
float4 color = image.Sample(textureSampler, v_in.uv);
16+
17+
// Split screen based on UV coordinate
18+
if (v_in.uv.x < 0.5) {
19+
// Left half: audio_peak (instantaneous spikes, more reactive)
20+
// Tint with red to show peak activity.
21+
float peak_strength = audio_peak * intensity;
22+
float3 peak_color = color.rgb + float3(peak_strength, 0, 0);
23+
return float4(peak_color, color.a);
24+
} else {
25+
// Right half: audio_magnitude (RMS/averaged levels, smoother)
26+
// Tint with blue to show magnitude activity.
27+
float mag_strength = audio_magnitude * intensity;
28+
float3 mag_color = color.rgb + float3(0, 0, mag_strength);
29+
return float4(mag_color, color.a);
30+
}
31+
}
32+
33+
/*
34+
EXPLANATION:
35+
- audio_peak: Shows instantaneous maximum levels, very responsive to drums/percussion.
36+
- audio_magnitude: Shows RMS (Root Mean Square) levels, smoother and represents sustained audio.
37+
38+
TYPICAL BEHAVIOR:
39+
- With music containing drums: Left side (peak) will flash more dramatically on beats.
40+
- With sustained tones: Right side (magnitude) will show more consistent levels.
41+
- Peak reacts faster to sudden sounds, magnitude is more stable for smooth effects.
42+
*/

obs-shaderfilter.c

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <stdio.h>
1616
#include <time.h>
1717
#include <string.h>
18+
#include <math.h>
1819

1920
#include <util/threading.h>
2021
#ifdef _WIN32
@@ -225,6 +226,8 @@ struct shader_filter_data {
225226
gs_eparam_t *param_transition_time;
226227
gs_eparam_t *param_convert_linear;
227228
gs_eparam_t *param_previous_output;
229+
gs_eparam_t *param_audio_peak;
230+
gs_eparam_t *param_audio_magnitude;
228231

229232
int expand_left;
230233
int expand_right;
@@ -247,6 +250,13 @@ struct shader_filter_data {
247250
float rand_f;
248251
float rand_instance_f;
249252
float rand_activation_f;
253+
float audio_peak;
254+
float audio_magnitude;
255+
256+
char *audio_source_name;
257+
obs_volmeter_t *volmeter;
258+
float current_audio_peak;
259+
float current_audio_magnitude;
250260

251261
DARRAY(struct effect_param_data) stored_param_list;
252262
};
@@ -351,6 +361,8 @@ static void shader_filter_clear_params(struct shader_filter_data *filter)
351361
filter->param_loops = NULL;
352362
filter->param_loop_second = NULL;
353363
filter->param_local_time = NULL;
364+
filter->param_audio_peak = NULL;
365+
filter->param_audio_magnitude = NULL;
354366
filter->param_image = NULL;
355367
filter->param_previous_image = NULL;
356368
filter->param_image_a = NULL;
@@ -612,6 +624,10 @@ static void shader_filter_reload_effect(struct shader_filter_data *filter)
612624
filter->param_loop_second = param;
613625
} else if (strcmp(info.name, "local_time") == 0) {
614626
filter->param_local_time = param;
627+
} else if (strcmp(info.name, "audio_peak") == 0) {
628+
filter->param_audio_peak = param;
629+
} else if (strcmp(info.name, "audio_magnitude") == 0) {
630+
filter->param_audio_magnitude = param;
615631
} else if (strcmp(info.name, "ViewProj") == 0) {
616632
// Nothing.
617633
} else if (strcmp(info.name, "image") == 0) {
@@ -742,6 +758,11 @@ static void shader_filter_destroy(void *data)
742758
dstr_free(&filter->last_path);
743759
da_free(filter->stored_param_list);
744760

761+
if (filter->volmeter)
762+
obs_volmeter_destroy(filter->volmeter);
763+
if (filter->audio_source_name)
764+
bfree(filter->audio_source_name);
765+
745766
bfree(filter);
746767
}
747768

@@ -2097,6 +2118,53 @@ static bool shader_filter_convert(obs_properties_t *props, obs_property_t *prope
20972118

20982119
static const char *shader_filter_texture_file_filter = "Textures (*.bmp *.tga *.png *.jpeg *.jpg *.gif);;";
20992120

2121+
#define MIN_AUDIO_THRESHOLD -60.0f
2122+
2123+
static float convert_db_to_linear(float db_value)
2124+
{
2125+
if (db_value <= MIN_AUDIO_THRESHOLD || db_value > 0.0f)
2126+
return 0.0f;
2127+
2128+
return fmaxf(0.0f, fminf(1.0f, (db_value - MIN_AUDIO_THRESHOLD) / (-MIN_AUDIO_THRESHOLD)));
2129+
}
2130+
2131+
static void shader_filter_audio_callback(void *data, const float magnitude[MAX_AUDIO_CHANNELS],
2132+
const float peak[MAX_AUDIO_CHANNELS], const float input_peak[MAX_AUDIO_CHANNELS])
2133+
{
2134+
UNUSED_PARAMETER(input_peak);
2135+
struct shader_filter_data *filter = (struct shader_filter_data *)data;
2136+
2137+
float max_peak = MIN_AUDIO_THRESHOLD;
2138+
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
2139+
if (peak[i] > max_peak && peak[i] != 0.0f) {
2140+
max_peak = peak[i];
2141+
}
2142+
}
2143+
2144+
float max_magnitude = MIN_AUDIO_THRESHOLD;
2145+
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
2146+
if (magnitude[i] > max_magnitude && magnitude[i] != 0.0f) {
2147+
max_magnitude = magnitude[i];
2148+
}
2149+
}
2150+
2151+
filter->current_audio_peak = convert_db_to_linear(max_peak);
2152+
filter->current_audio_magnitude = convert_db_to_linear(max_magnitude);
2153+
}
2154+
2155+
static bool shader_filter_enum_audio_sources(void *data, obs_source_t *source)
2156+
{
2157+
obs_property_t *prop = (obs_property_t *)data;
2158+
uint32_t flags = obs_source_get_output_flags(source);
2159+
2160+
if ((flags & OBS_SOURCE_AUDIO) != 0) {
2161+
const char *name = obs_source_get_name(source);
2162+
obs_property_list_add_string(prop, name, name);
2163+
}
2164+
2165+
return true;
2166+
}
2167+
21002168
static obs_properties_t *shader_filter_properties(void *data)
21012169
{
21022170
struct shader_filter_data *filter = data;
@@ -2150,6 +2218,14 @@ static obs_properties_t *shader_filter_properties(void *data)
21502218
obs_properties_add_button(props, "reload_effect", obs_module_text("ShaderFilter.ReloadEffect"),
21512219
shader_filter_reload_effect_clicked);
21522220

2221+
if (filter && (filter->param_audio_magnitude || filter->param_audio_peak)) {
2222+
obs_property_t *audio_source = obs_properties_add_list(props, "audio_source", "Audio source", OBS_COMBO_TYPE_LIST,
2223+
OBS_COMBO_FORMAT_STRING);
2224+
obs_property_list_add_string(audio_source, "None", "");
2225+
2226+
obs_enum_sources(shader_filter_enum_audio_sources, audio_source);
2227+
}
2228+
21532229
DARRAY(obs_property_t *) groups;
21542230
da_init(groups);
21552231

@@ -2393,6 +2469,56 @@ static void shader_filter_update(void *data, obs_data_t *settings)
23932469
obs_source_update_properties(filter->context);
23942470
}
23952471

2472+
if (filter->param_audio_magnitude || filter->param_audio_peak) {
2473+
const char *audio_source_name = obs_data_get_string(settings, "audio_source");
2474+
if (!filter->audio_source_name || strcmp(filter->audio_source_name, audio_source_name) != 0) {
2475+
obs_source_t *audio_source = strlen(audio_source_name) > 0 ? obs_get_source_by_name(audio_source_name) : NULL;
2476+
if (audio_source && ((obs_source_get_output_flags(audio_source) & OBS_SOURCE_AUDIO) == 0)) {
2477+
obs_source_release(audio_source);
2478+
audio_source = NULL;
2479+
}
2480+
if (audio_source) {
2481+
if (filter->audio_source_name)
2482+
bfree(filter->audio_source_name);
2483+
filter->audio_source_name = bstrdup(audio_source_name);
2484+
}
2485+
2486+
if (!audio_source) {
2487+
audio_source = obs_source_get_ref(obs_filter_get_parent(filter->context));
2488+
if (audio_source && ((obs_source_get_output_flags(audio_source) & OBS_SOURCE_AUDIO) == 0)) {
2489+
obs_source_release(audio_source);
2490+
audio_source = NULL;
2491+
}
2492+
}
2493+
if (audio_source) {
2494+
if (!filter->volmeter) {
2495+
filter->volmeter = obs_volmeter_create(OBS_FADER_LOG);
2496+
obs_volmeter_add_callback(filter->volmeter, shader_filter_audio_callback, filter);
2497+
}
2498+
obs_volmeter_attach_source(filter->volmeter, audio_source);
2499+
obs_source_release(audio_source);
2500+
} else {
2501+
if (filter->volmeter) {
2502+
obs_volmeter_destroy(filter->volmeter);
2503+
filter->volmeter = NULL;
2504+
}
2505+
if (filter->audio_source_name) {
2506+
bfree(filter->audio_source_name);
2507+
filter->audio_source_name = NULL;
2508+
}
2509+
}
2510+
}
2511+
} else {
2512+
if (filter->volmeter) {
2513+
obs_volmeter_destroy(filter->volmeter);
2514+
filter->volmeter = NULL;
2515+
}
2516+
if (filter->audio_source_name) {
2517+
bfree(filter->audio_source_name);
2518+
filter->audio_source_name = NULL;
2519+
}
2520+
}
2521+
23962522
size_t param_count = filter->stored_param_list.num;
23972523
for (size_t param_index = 0; param_index < param_count; param_index++) {
23982524
struct effect_param_data *param = (filter->stored_param_list.array + param_index);
@@ -2664,6 +2790,14 @@ static void shader_filter_tick(void *data, float seconds)
26642790
// undecided between this and "rand_float(1);"
26652791
filter->rand_f = (float)((double)rand_interval(0, 10000) / (double)10000);
26662792

2793+
if (filter->volmeter) {
2794+
filter->audio_peak = filter->current_audio_peak;
2795+
filter->audio_magnitude = filter->current_audio_magnitude;
2796+
} else {
2797+
filter->audio_peak = 0.0f;
2798+
filter->audio_magnitude = 0.0f;
2799+
}
2800+
26672801
filter->output_rendered = false;
26682802
filter->input_rendered = false;
26692803
}
@@ -2832,6 +2966,12 @@ void shader_filter_set_effect_params(struct shader_filter_data *filter)
28322966
if (filter->param_local_time != NULL) {
28332967
gs_effect_set_float(filter->param_local_time, filter->local_time);
28342968
}
2969+
if (filter->param_audio_peak != NULL) {
2970+
gs_effect_set_float(filter->param_audio_peak, filter->audio_peak);
2971+
}
2972+
if (filter->param_audio_magnitude != NULL) {
2973+
gs_effect_set_float(filter->param_audio_magnitude, filter->audio_magnitude);
2974+
}
28352975
if (filter->param_loops != NULL) {
28362976
gs_effect_set_int(filter->param_loops, filter->loops);
28372977
}

0 commit comments

Comments
 (0)