summaryrefslogtreecommitdiff
path: root/scripts/scope.lua
blob: 1952c100c431151fa6ac3534105cf646618c8a70 (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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
ardour {
	["type"]    = "dsp",
	name        = "a-Inline Scope",
	category    = "Visualization",
	license     = "MIT",
	author      = "Ardour Team",
	description = [[Mixer strip inline waveform display]]
}

-- return possible i/o configurations
function dsp_ioconfig ()
	-- -1, -1 = any number of channels as long as input and output count matches
	return { [1] = { audio_in = -1, audio_out = -1}, }
end

function dsp_params ()
	return
	{
		{ ["type"] = "input", name = "Timescale", min = .1, max = 5, default = 2, unit="sec", logarithmic = true },
		{ ["type"] = "input", name = "Logscale", min = 0, max = 1, default = 0, toggled = true },
		{ ["type"] = "input", name = "Height (Aspect)", min = 0, max = 3, default = 1, enum = true, scalepoints =
			{
				["Min"] = 0,
				["16:10"] = 1,
				["1:1"] = 2,
				["Max"] = 3
			}
		},
	}
end


function dsp_init (rate)
	-- global variables (DSP part only)
	samplerate = rate
	bufsiz = 6 * rate
	dpy_hz = rate / 25
	dpy_wr = 0
end

function dsp_configure (ins, outs)
	-- store configuration in global variable
	audio_ins = ins:n_audio ()
	-- allocate shared memory area
	-- this is used to speed up DSP computaton (using a C array)
	-- and to share data with the GUI
	self:shmem ():allocate (4 + bufsiz * audio_ins)
	self:shmem ():clear ()
	self:shmem ():atomic_set_int (0, 0)
	local cfg = self:shmem ():to_int (1):array ()
	cfg[1] = samplerate
	cfg[2] = bufsiz
	cfg[3] = audio_ins
end

function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
	local shmem = self:shmem ()
	local write_ptr = shmem:atomic_get_int (0)

	for c = 1,audio_ins do
		-- Note: lua starts counting at 1, ardour's ChanMapping::get() at 0
		local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped input buffer for given cannel
		local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped output buffer for given cannel
		local chn_off = 4 + bufsiz * (c - 1)
		if (ib ~= ARDOUR.ChanMapping.Invalid) then
			if (write_ptr + n_samples < bufsiz) then
				ARDOUR.DSP.copy_vector (shmem:to_float (write_ptr + chn_off), bufs:get_audio (ib):data (offset), n_samples)
			else
				local w0 = bufsiz - write_ptr;
				ARDOUR.DSP.copy_vector (shmem:to_float (write_ptr + chn_off), bufs:get_audio (ib):data (offset), w0)
				ARDOUR.DSP.copy_vector (shmem:to_float (chn_off)            , bufs:get_audio (ib):data (offset + w0), n_samples - w0)
			end
			if (ob ~= ARDOUR.ChanMapping.Invalid and ib ~= ob) then
				ARDOUR.DSP.copy_vector (bufs:get_audio (ob):data (offset), bufs:get_audio (ib):data (offset), n_samples)
			end
		else
			if (write_ptr + n_samples < bufsiz) then
				ARDOUR.DSP.memset (shmem:to_float (write_ptr + chn_off), 0, n_samples)
			else
				local w0 = bufsiz - write_ptr;
				ARDOUR.DSP.memset (shmem:to_float (write_ptr + chn_off), 0, w0)
				ARDOUR.DSP.memset (shmem:to_float (chn_off)            , 0, n_samples - w0)
			end
		end
	end
	-- clear unconnected inplace buffers
	for c = 1,audio_ins do
		local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped input buffer for given cannel
		local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped output buffer for given cannel
		if (ib == ARDOUR.ChanMapping.Invalid and ob ~= ARDOUR.ChanMapping.Invalid) then
			bufs:get_audio (ob):silence (n_samples, offset)
		end
	end

	write_ptr = (write_ptr + n_samples) % bufsiz
	shmem:atomic_set_int (0, write_ptr)

	-- emit QueueDraw every FPS
	dpy_wr = dpy_wr + n_samples
	if (dpy_wr > dpy_hz) then
		dpy_wr = dpy_wr % dpy_hz;
		self:queue_draw ()
	end
end


-- helper function for drawing symmetric grid
function gridline (ctx, x, xr, h, val)
	ctx:move_to (math.floor (.5 + x + val * xr) -.5, 1)
	ctx:line_to (math.floor (.5 + x + val * xr) -.5, h - 1)
	ctx:stroke ()
	ctx:move_to (math.floor (.5 + x - val * xr) -.5, 1)
	ctx:line_to (math.floor (.5 + x - val * xr) -.5, h - 1)
	ctx:stroke ()
end

function render_inline (ctx, w, max_h)
	local ctrl = CtrlPorts:array () -- get control port array (read/write)
	local shmem = self:shmem () -- get shared memory region
	local cfg = shmem:to_int (1):array () -- "cast" into lua-table
	local rate = cfg[1]
	local buf_size = cfg[2]
	local n_chn = cfg[3]

	-- get settings
	local timescale = ctrl[1] or 1.0 -- display size in seconds
	local logscale = ctrl[2] or 0; logscale = logscale > 0 -- logscale
	local hmode = ctrl[3] or 1 -- height mode

	-- calc height
	if hmode == 0 then
		h = math.ceil (w * 10 / 16)
		if (h > 44) then
			h = 44
		end
	elseif (hmode == 2) then
		h = w
	elseif (hmode == 3) then
		h = max_h
	else
		h = math.ceil (w * 10 / 16)
	end

	if (h > max_h) then
		h = max_h
	end

	-- display settings
	local spp = math.floor (timescale * rate / (h - 2)) -- samples per pixel
	local spl = spp * (h - 1) -- total number of audio samples to read
	local read_ptr = (shmem:atomic_get_int (0) + buf_size - spl - 1) % buf_size -- read pointer
	local xr = math.ceil ((w - 2) * (0.47 / n_chn)) -- x-axis range (per channel)

	-- clear background
	ctx:rectangle (0, 0, w, h)
	ctx:set_source_rgba (.2, .2, .2, 1.0)
	ctx:fill ()

	-- prepare drawing
	ctx:set_line_width (1.0)
	local dash3 = C.DoubleVector ()
	dash3:add ({1, 3})
	local dash4 = C.DoubleVector ()
	dash4:add ({1, 4})

	-- plot every channel
	for c = 1,n_chn do
		local x = math.floor ((w - 2) * (c - .5) / n_chn) + 1.5  -- x-axis center for given channel

		-- draw grid --
		ctx:set_source_rgba (.5, .5, .5, 1.0)
		ctx:move_to (x, 1) ctx:line_to (x, h - 1) ctx:stroke ()

		ctx:set_dash (dash4, 2)
		ctx:set_source_rgba (.4, .4, .4, 1.0)
		if (logscale) then
			gridline (ctx, x, xr, h, ARDOUR.DSP.log_meter(-18))
			gridline (ctx, x, xr, h, ARDOUR.DSP.log_meter(-6))
			ctx:set_dash (dash3, 2)
			ctx:set_source_rgba (.5, .1, .1, 1.0)
			gridline (ctx, x, xr, h, ARDOUR.DSP.log_meter(-3))
		else
			gridline (ctx, x, xr, h, .1258)
			gridline (ctx, x, xr, h, .5)
			ctx:set_dash (dash3, 2)
			ctx:set_source_rgba (.5, .1, .1, 1.0)
			gridline (ctx, x, xr, h, .7079)
		end
		ctx:unset_dash ()
		ctx:set_source_rgba (.5, .1, .1, 0.7)
		gridline (ctx, x, xr, h, 1)


		-- prepare waveform display drawing
		ctx:set_source_rgba (.8, .8, .8, .7)
		ctx:save ()
		ctx:rectangle (math.floor (x - xr), 0, math.ceil (2 * xr), h)
		ctx:clip ()

		local chn_off = 4 + buf_size * (c - 1)
		local buf_off = read_ptr;

		-- iterate over every y-axis pixel
		for y = 1, h - 1 do
			local s_min = 0
			local s_max = 0
			-- calc min/max values for given range
			if (buf_off + spp < buf_size) then
				_, s_min, s_max = table.unpack (ARDOUR.DSP.peaks (shmem:to_float (chn_off + buf_off), s_min, s_max, spp))
			else
				local r0 = buf_size - buf_off;
				_, s_min, s_max = table.unpack (ARDOUR.DSP.peaks (shmem:to_float (chn_off + buf_off), s_min, s_max, r0))
				_, s_min, s_max = table.unpack (ARDOUR.DSP.peaks (shmem:to_float (chn_off)          , s_min, s_max, spp - r0))
			end
			buf_off = (buf_off + spp) % buf_size;

			if (logscale) then
				s_max = ARDOUR.DSP.log_meter_coeff (s_max)
				s_min = - ARDOUR.DSP.log_meter_coeff (-s_min)
			end

			ctx:move_to (x + s_min * xr, h - y + .5)
			ctx:line_to (x + s_max * xr, h - y + .5)
		end
		ctx:stroke ()
		ctx:restore ()
	end
	return {w, h}
end