summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien "_FrnchFrgg_" RIVAUD <frnchfrgg@free.fr>2017-07-24 14:54:10 +0200
committerJulien "_FrnchFrgg_" RIVAUD <frnchfrgg@free.fr>2017-07-24 15:05:05 +0200
commita9d2955f833fc568733d11561ec1156ae3f9a1da (patch)
tree327455c7566aa67507f0eb4b9bf938e91608042b
parentf1632fcfd2eeed678da330f77bdabfc766ade826 (diff)
Replace a ringbuffer with a multi-reader circular table
a-Inline Spectrogram used a ringbuffer to send mixed down audio data from the DSP thread to the inline display thread. The problem is that several inline display threads can coexist (one for the channel strip in the editor, one for the channel strip in the mixer, and soon one for an inline display in the generic plugin UI). A ringbuffer is single-writer single-reader so each display only got part of the data, and all were competing for it. Replace it with a circular table, where the DSP sets a write pointer, and every (inline display) user keeps its own read pointer and checks it is not so far in the past as to be overtaken by the DSP write pointer.
-rw-r--r--scripts/spectrogram.lua85
1 files changed, 59 insertions, 26 deletions
diff --git a/scripts/spectrogram.lua b/scripts/spectrogram.lua
index 4f55a654a7..86419682b4 100644
--- a/scripts/spectrogram.lua
+++ b/scripts/spectrogram.lua
@@ -40,6 +40,11 @@ function dsp_params ()
}
end
+-- symbolic names for shmem offsets
+local SHMEM_RATE = 0
+local SHMEM_WRITEPTR = 1
+local SHMEM_AUDIO = 2
+
-- a C memory area.
-- It needs to be in global scope.
-- When the variable is set to nil, the allocated memory is free()ed.
@@ -53,36 +58,34 @@ function dsp_init (rate)
dpy_hz = rate / 25
dpy_wr = 0
- -- create a ringbuffer to hold (float) audio-data
- -- http://manual.ardour.org/lua-scripting/class_reference/#PBD:RingBufferF
- rb = PBD.RingBufferF (2 * rate)
+ -- create a shared memory area to hold the sample rate, the write_pointer,
+ -- and (float) audio-data. Make it big enough to store 2s of audio which
+ -- should be enough. If not, the DSP will overwrite the oldest data anyway.
+ self:shmem ():allocate(2 + 2 * rate)
+ self:shmem ():clear()
+ self:shmem ():atomic_set_int (SHMEM_RATE, rate)
+ self:shmem ():atomic_set_int (SHMEM_WRITEPTR, 0)
-- allocate memory, local mix buffer
cmem = ARDOUR.DSP.DspShm (8192)
-
- -- create a table of objects to share with the GUI
- local tbl = {}
- tbl['rb'] = rb;
- tbl['samplerate'] = rate
-
- -- "self" is a special DSP variable referring
- -- to the plugin instance itself.
- --
- -- "table()" is-a http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR.LuaTableRef
- -- which allows to store/retrieve lua-tables to share them other interpreters
- self:table ():set (tbl);
end
-- "dsp_runmap" uses Ardour's internal processor API, eqivalent to
-- 'connect_and_run()". There is no overhead (mapping, translating buffers).
-- The lua implementation is responsible to map all the buffers directly.
function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
- -- here we sum all audio input channels channels and then copy the data to a ringbuffer
- -- for the GUI to process later
+ -- here we sum all audio input channels and then copy the data to a
+ -- custom-made circular table for the GUIs to process later
local audio_ins = in_map:count (): n_audio () -- number of audio input buffers
local ccnt = 0 -- processed channel count
local mem = cmem:to_float(0) -- a "FloatArray", float* for direct C API usage from the previously allocated buffer
+ local rate = self:shmem ():atomic_get_int (SHMEM_RATE)
+ local write_ptr = self:shmem ():atomic_get_int (SHMEM_WRITEPTR)
+
+ local ringsize = 2 * rate
+ local ptr_wrap = math.floor(2^50 / ringsize) * ringsize
+
for c = 1,audio_ins do
-- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:ChanMapping
-- Note: lua starts counting at 1, ardour's ChanMapping::get() at 0
@@ -131,9 +134,15 @@ function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
ARDOUR.DSP.memset (mem, 0, n_samples)
end
- -- write data to the ringbuffer
- -- http://manual.ardour.org/lua-scripting/class_reference/#PBD:RingBufferF
- rb:write (mem, n_samples)
+ -- write data to the circular table
+ if (write_ptr % ringsize + n_samples < ringsize) then
+ ARDOUR.DSP.copy_vector (self:shmem ():to_float (SHMEM_AUDIO + write_ptr % ringsize), mem, n_samples)
+ else
+ local chunk = ringsize - write_ptr % ringsize
+ ARDOUR.DSP.copy_vector (self:shmem ():to_float (SHMEM_AUDIO + write_ptr % ringsize), mem, chunk)
+ ARDOUR.DSP.copy_vector (self:shmem ():to_float (SHMEM_AUDIO), cmem:to_float (chunk), n_samples - chunk)
+ end
+ self:shmem ():atomic_set_int (SHMEM_WRITEPTR, (write_ptr + n_samples) % ptr_wrap)
-- emit QueueDraw every FPS
-- TODO: call every FFT window-size worth of samples, at most every FPS
@@ -154,10 +163,10 @@ local img = nil
local fft_size = 0
local last_log = false
+
function render_inline (ctx, w, max_h)
local ctrl = CtrlPorts:array () -- get control port array (read/write)
- local tbl = self:table ():get () -- get shared memory table
- local rate = tbl['samplerate']
+ local rate = self:shmem ():atomic_get_int (SHMEM_RATE)
if not cmem then
cmem = ARDOUR.DSP.DspShm (0)
end
@@ -231,12 +240,36 @@ function render_inline (ctx, w, max_h)
local f_b = w / math.log (fft_size / 2) -- inverse log-scale base
local f_l = math.log (fft_size / rate) * f_b -- inverse logscale lower-bound
- local rb = tbl['rb'];
local mem = cmem:to_float (0)
- while (rb:read_space() >= fft_size) do
- -- process one line / buffer
- rb:read (mem, fft_size)
+ local ringsize = 2 * rate
+ local ptr_wrap = math.floor(2^50 / ringsize) * ringsize
+
+ local write_ptr
+ function read_space()
+ write_ptr = self:shmem ():atomic_get_int (SHMEM_WRITEPTR)
+ local space = (write_ptr - read_ptr + ptr_wrap) % ptr_wrap
+ if space > ringsize then
+ -- the GUI lagged too much and unread data was overwritten
+ -- jump to the oldest audio still present in the ringtable
+ read_ptr = write_ptr - ringsize
+ space = ringsize
+ end
+ return space
+ end
+
+ while (read_space() >= fft_size) do
+ -- read one window from the circular table
+ if (read_ptr % ringsize + fft_size < ringsize) then
+ ARDOUR.DSP.copy_vector (mem, self:shmem ():to_float (SHMEM_AUDIO + read_ptr % ringsize), fft_size)
+ else
+ local chunk = ringsize - read_ptr % ringsize
+ ARDOUR.DSP.copy_vector (mem, self:shmem ():to_float (SHMEM_AUDIO + read_ptr % ringsize), chunk)
+ ARDOUR.DSP.copy_vector (cmem:to_float(chunk), self:shmem ():to_float (SHMEM_AUDIO), fft_size - chunk)
+ end
+ read_ptr = (read_ptr + fft_size) % ptr_wrap
+
+ -- process one line
fft:set_data_hann (mem, fft_size, 0)
fft:execute ()