From cc45b92a8eb41b39b69304bf2de7b2b4a8d6e9b7 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Fri, 19 Jun 2015 21:51:40 -0700 Subject: [PATCH] obs-filters: Add noise gate filter --- plugins/obs-filters/CMakeLists.txt | 1 + plugins/obs-filters/data/locale/en-US.ini | 6 + plugins/obs-filters/noise-gate-filter.c | 222 ++++++++++++++++++++++ plugins/obs-filters/obs-filters.c | 2 + 4 files changed, 231 insertions(+) create mode 100644 plugins/obs-filters/noise-gate-filter.c diff --git a/plugins/obs-filters/CMakeLists.txt b/plugins/obs-filters/CMakeLists.txt index c7d7db952..8cfdb85c5 100644 --- a/plugins/obs-filters/CMakeLists.txt +++ b/plugins/obs-filters/CMakeLists.txt @@ -9,6 +9,7 @@ set(obs-filters_SOURCES chroma-key-filter.c color-key-filter.c sharpness-filter.c + noise-gate-filter.c mask-filter.c) add_library(obs-filters MODULE diff --git a/plugins/obs-filters/data/locale/en-US.ini b/plugins/obs-filters/data/locale/en-US.ini index cb82b5b1c..f92be3e96 100644 --- a/plugins/obs-filters/data/locale/en-US.ini +++ b/plugins/obs-filters/data/locale/en-US.ini @@ -6,6 +6,7 @@ ScrollFilter="Scroll" ChromaKeyFilter="Chroma Key" ColorKeyFilter="Color Key" SharpnessFilter="Sharpen" +NoiseGate="Noise Gate" DelayMs="Delay (milliseconds)" Type="Type" MaskBlendType.MaskColor="Alpha Mask (Color Channel)" @@ -42,3 +43,8 @@ Red="Red" Green="Green" Blue="Blue" Magenta="Magenta" +NoiseGate.OpenThreshold="Open Threshold (db)" +NoiseGate.CloseThreshold="Close Threshold (db)" +NoiseGate.AttackTime="Attack Time (milliseconds)" +NoiseGate.HoldTime="Hold Time (milliseconds)" +NoiseGate.ReleaseTime="Release Time (milliseconds)" diff --git a/plugins/obs-filters/noise-gate-filter.c b/plugins/obs-filters/noise-gate-filter.c new file mode 100644 index 000000000..43b7f9c06 --- /dev/null +++ b/plugins/obs-filters/noise-gate-filter.c @@ -0,0 +1,222 @@ +#include +#include + +#ifdef _MSC_VER +#include + +#ifndef finite +#define finite _finite +#endif + +static inline float fmaxf(float a, float b) +{ + return a > b ? a : b; +} + +static inline float fminf(float a, float b) +{ + return a < b ? a : b; +} + +#endif + +#define do_log(level, format, ...) \ + blog(level, "[noise gate: '%s'] " format, \ + obs_source_get_name(ng->context), ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) +#define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) + +#define S_OPEN_THRESHOLD "open_threshold" +#define S_CLOSE_THRESHOLD "close_threshold" +#define S_ATTACK_TIME "attack_time" +#define S_HOLD_TIME "hold_time" +#define S_RELEASE_TIME "release_time" + +#define MT_ obs_module_text +#define TEXT_OPEN_THRESHOLD MT_("NoiseGate.OpenThreshold") +#define TEXT_CLOSE_THRESHOLD MT_("NoiseGate.CloseThreshold") +#define TEXT_ATTACK_TIME MT_("NoiseGate.AttackTime") +#define TEXT_HOLD_TIME MT_("NoiseGate.HoldTime") +#define TEXT_RELEASE_TIME MT_("NoiseGate.ReleaseTime") + +struct noise_gate_data { + obs_source_t *context; + + float sample_rate_i; + size_t channels; + + float open_threshold; + float close_threshold; + float decay_rate; + float attack_rate; + float release_rate; + float hold_time; + + bool is_open; + float attenuation; + float level; + float held_time; +}; + +#define VOL_MIN -96.0f +#define VOL_MAX 0.0f + +static inline float rms_to_db(float rms) +{ + float db = 20.0f * log10f(rms); + return finite(db) ? db : VOL_MIN; +} + +static inline float db_to_rms(float db) +{ + return powf(10.0f, db / 20.0f); +} + +static const char *noise_gate_name(void) +{ + return obs_module_text("NoiseGate"); +} + +static void noise_gate_destroy(void *data) +{ + struct noise_gate_data *ng = data; + bfree(ng); +} + +static inline float ms_to_secf(int ms) +{ + return (float)ms / 1000.0f; +} + +static void noise_gate_update(void *data, obs_data_t *s) +{ + struct noise_gate_data *ng = data; + float open_threshold_db; + float close_threshold_db; + float sample_rate; + int attack_time_ms; + int hold_time_ms; + int release_time_ms; + + open_threshold_db = (float)obs_data_get_double(s, S_OPEN_THRESHOLD); + close_threshold_db = (float)obs_data_get_double(s, S_CLOSE_THRESHOLD); + attack_time_ms = (int)obs_data_get_int(s, S_ATTACK_TIME); + hold_time_ms = (int)obs_data_get_int(s, S_HOLD_TIME); + release_time_ms = (int)obs_data_get_int(s, S_RELEASE_TIME); + sample_rate = (float)audio_output_get_sample_rate(obs_get_audio()); + + ng->sample_rate_i = 1.0f / sample_rate; + ng->channels = audio_output_get_channels(obs_get_audio()); + ng->open_threshold = db_to_rms(open_threshold_db); + ng->close_threshold = db_to_rms(close_threshold_db); + ng->attack_rate = 1.0f / (ms_to_secf(attack_time_ms) * sample_rate); + ng->release_rate = 1.0f / (ms_to_secf(release_time_ms) * sample_rate); + + const float threshold_diff = ng->open_threshold - ng->close_threshold; + const float min_decay_period = (1.0f / 75.0f) * sample_rate; + + ng->decay_rate = threshold_diff / min_decay_period; + ng->hold_time = ms_to_secf(hold_time_ms); + ng->is_open = false; + ng->attenuation = 0.0f; + ng->level = 0.0f; + ng->held_time = 0.0f; +} + +static void *noise_gate_create(obs_data_t *settings, obs_source_t *filter) +{ + struct noise_gate_data *ng = bzalloc(sizeof(*ng)); + ng->context = filter; + noise_gate_update(ng, settings); + return ng; +} + +static struct obs_audio_data *noise_gate_filter_audio(void *data, + struct obs_audio_data *audio) +{ + struct noise_gate_data *ng = data; + + float *adata[2] = {(float*)audio->data[0], (float*)audio->data[1]}; + const float close_threshold = ng->close_threshold; + const float open_threshold = ng->open_threshold; + const float sample_rate_i = ng->sample_rate_i; + const float release_rate = ng->release_rate; + const float attack_rate = ng->attack_rate; + const float decay_rate = ng->decay_rate; + const float hold_time = ng->hold_time; + const size_t channels = ng->channels; + + for (size_t i = 0; i < audio->frames; i++) { + float cur_level = (channels == 2) + ? (fabsf(adata[0][i] + adata[1][i]) * 0.5f) + : (fabsf(adata[0][i]) * 0.5f); + + if (cur_level > open_threshold && !ng->is_open) { + ng->is_open = true; + } + if (ng->level < close_threshold && ng->is_open) { + ng->held_time = 0.0f; + ng->is_open = false; + } + + ng->level = fmaxf(ng->level, cur_level) - decay_rate; + + if (ng->is_open) { + ng->attenuation = fminf(1.0f, + ng->attenuation + attack_rate); + } else { + ng->held_time += sample_rate_i; + if (ng->held_time > hold_time) { + ng->attenuation = fmaxf(0.0f, + ng->attenuation - release_rate); + } + } + + for (size_t c = 0; c < channels; c++) + adata[c][i] *= ng->attenuation; + } + + return audio; +} + +static void noise_gate_defaults(obs_data_t *s) +{ + obs_data_set_default_double(s, S_OPEN_THRESHOLD, -26.0f); + obs_data_set_default_double(s, S_CLOSE_THRESHOLD, -32.0f); + obs_data_set_default_int (s, S_ATTACK_TIME, 25); + obs_data_set_default_int (s, S_HOLD_TIME, 200); + obs_data_set_default_int (s, S_RELEASE_TIME, 150); +} + +static obs_properties_t *noise_gate_properties(void *data) +{ + obs_properties_t *ppts = obs_properties_create(); + + obs_properties_add_float_slider(ppts, S_CLOSE_THRESHOLD, + TEXT_CLOSE_THRESHOLD, VOL_MIN, VOL_MAX, 1.0f); + obs_properties_add_float_slider(ppts, S_OPEN_THRESHOLD, + TEXT_OPEN_THRESHOLD, VOL_MIN, VOL_MAX, 1.0f); + obs_properties_add_int(ppts, S_ATTACK_TIME, TEXT_ATTACK_TIME, + 0, 10000, 1); + obs_properties_add_int(ppts, S_HOLD_TIME, TEXT_HOLD_TIME, + 0, 10000, 1); + obs_properties_add_int(ppts, S_RELEASE_TIME, TEXT_RELEASE_TIME, + 0, 10000, 1); + + UNUSED_PARAMETER(data); + return ppts; +} + +struct obs_source_info noise_gate_filter = { + .id = "noise_gate_filter", + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_AUDIO, + .get_name = noise_gate_name, + .create = noise_gate_create, + .destroy = noise_gate_destroy, + .update = noise_gate_update, + .filter_audio = noise_gate_filter_audio, + .get_defaults = noise_gate_defaults, + .get_properties = noise_gate_properties, +}; diff --git a/plugins/obs-filters/obs-filters.c b/plugins/obs-filters/obs-filters.c index c3f3afcf0..d9192298a 100644 --- a/plugins/obs-filters/obs-filters.c +++ b/plugins/obs-filters/obs-filters.c @@ -12,6 +12,7 @@ extern struct obs_source_info color_key_filter; extern struct obs_source_info sharpness_filter; extern struct obs_source_info chroma_key_filter; extern struct obs_source_info async_delay_filter; +extern struct obs_source_info noise_gate_filter; bool obs_module_load(void) { @@ -23,5 +24,6 @@ bool obs_module_load(void) obs_register_source(&sharpness_filter); obs_register_source(&chroma_key_filter); obs_register_source(&async_delay_filter); + obs_register_source(&noise_gate_filter); return true; }