summaryrefslogtreecommitdiffstats
path: root/TOOLS/lua/acompressor.lua
blob: 6a6914076a968325309d6d3f087af6e22481bcba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
-- This script adds control to the dynamic range compression ffmpeg
-- filter including key bindings for adjusting parameters.
--
-- See https://ffmpeg.org/ffmpeg-filters.html#acompressor for explanation
-- of the parameters.

local mp = require 'mp'
local options = require 'mp.options'

local o = {
	default_enable = false,
	show_osd = true,
	osd_timeout = 4000,
	filter_label = mp.get_script_name(),

	key_toggle = 'n',
	key_increase_threshold = 'F1',
	key_decrease_threshold = 'Shift+F1',
	key_increase_ratio = 'F2',
	key_decrease_ratio = 'Shift+F2',
	key_increase_knee = 'F3',
	key_decrease_knee = 'Shift+F3',
	key_increase_makeup = 'F4',
	key_decrease_makeup = 'Shift+F4',
	key_increase_attack = 'F5',
	key_decrease_attack = 'Shift+F5',
	key_increase_release = 'F6',
	key_decrease_release = 'Shift+F6',

	default_threshold = -25.0,
	default_ratio = 3.0,
	default_knee = 2.0,
	default_makeup = 8.0,
	default_attack = 20.0,
	default_release = 250.0,

	step_threshold = -2.5,
	step_ratio = 1.0,
	step_knee = 1.0,
	step_makeup = 1.0,
	step_attack = 10.0,
	step_release = 10.0,
}
options.read_options(o)

local params = {
	{ name = 'attack',    min=0.01, max=2000, hide_default=true,  dB=''   },
	{ name = 'release',   min=0.01, max=9000, hide_default=true,  dB=''   },
	{ name = 'threshold', min= -30, max=   0, hide_default=false, dB='dB' },
	{ name = 'ratio',     min=   1, max=  20, hide_default=false, dB=''   },
	{ name = 'knee',      min=   1, max=  10, hide_default=true,  dB='dB' },
	{ name = 'makeup',    min=   0, max=  24, hide_default=false, dB='dB' },
}

local function parse_value(value)
	-- Using nil here because tonumber differs between lua 5.1 and 5.2 when parsing fractions in combination with explicit base argument set to 10.
	-- And we can't omit it because gsub returns 2 values which would get unpacked and cause more problems. Gotta love scripting languages.
	return tonumber(value:gsub('dB$', ''), nil)
end

local function format_value(value, dB)
	return string.format('%g%s', value, dB)
end

local function show_osd(filter)
	if not o.show_osd then
		return
	end

	if not filter.enabled then
		mp.commandv('show-text', 'Dynamic range compressor: disabled', o.osd_timeout)
		return
	end

	local pretty = {}
	for _,param in ipairs(params) do
		local value = parse_value(filter.params[param.name])
		if not (param.hide_default and value == o['default_' .. param.name]) then
			pretty[#pretty+1] = string.format('%s: %g%s', param.name:gsub("^%l", string.upper), value, param.dB)
		end
	end

	if #pretty == 0 then
		pretty = ''
	else
		pretty = '\n(' .. table.concat(pretty, ', ') .. ')'
	end

	mp.commandv('show-text', 'Dynamic range compressor: enabled' .. pretty, o.osd_timeout)
end

local function get_filter()
	local af = mp.get_property_native('af', {})

	for i = 1, #af do
		if af[i].label == o.filter_label then
			return af, i
		end
	end

	af[#af+1] = {
		name = 'acompressor',
		label = o.filter_label,
		enabled = false,
		params = {},
	}

	for _,param in pairs(params) do
		af[#af].params[param.name] = format_value(o['default_' .. param.name], param.dB)
	end

	return af, #af
end

local function toggle_acompressor()
	local af, i = get_filter()
	af[i].enabled = not af[i].enabled
	mp.set_property_native('af', af)
	show_osd(af[i])
end

local function update_param(name, increment)
	for _,param in pairs(params) do
		if param.name == string.lower(name) then
			local af, i = get_filter()
			local value = parse_value(af[i].params[param.name])
			value = math.max(param.min, math.min(value + increment, param.max))
			af[i].params[param.name] = format_value(value, param.dB)
			af[i].enabled = true
			mp.set_property_native('af', af)
			show_osd(af[i])
			return
		end
	end

	mp.msg.error('Unknown parameter "' .. name .. '"')
end

mp.add_key_binding(o.key_toggle, "toggle-acompressor", toggle_acompressor)
mp.register_script_message('update-param', update_param)

for _,param in pairs(params) do
	for direction,step in pairs({increase=1, decrease=-1}) do
		mp.add_key_binding(o['key_' .. direction .. '_' .. param.name],
		                   'acompressor-' .. direction .. '-' .. param.name,
		                   function() update_param(param.name, step*o['step_' .. param.name]); end,
		                   { repeatable = true })
	end
end

if o.default_enable then
	local af, i = get_filter()
	af[i].enabled = true
	mp.set_property_native('af', af)
end