How to store a matrix of impulse responses in a plugin? (re: Failed to compute constant value for nontunable property)

3 views (last 30 days)
I'm trying a build a convolver plugin that will cycle through many impulse responses. I started with the audiopluginexample.FastConvolver plugin from the Audio Plugin Example Gallery. I modified it to read in a long array of concatentated impulse responses and then split them out into a matrix to read them more easily.
When I run it in audioTestBench it works fine. When I attempt to run validateAudioPlugin I get this error:
Checking plugin class 'ggConvolv'... passed.
Generating testbench file 'testbench_ggConvolv.m'... done.
Running testbench... passed.
Generating mex file 'testbench_ggConvolv_mex.mexmaci64'... Failed to compute constant value for nontunable property 'tdiMatrix'. In code generation, nontunable properties can only be assigned constant values.
Error in ==> ggConvolv Line: 38 Column: 13
Code generation failed: View Error Report
Error using coder.internal.generateAudioPlugin
Error in validateAudioPlugin
I don't understand the issue. tdiMatrix is a constant value, right?
Of course, I immediately tried simply making tdiMatrix tunable, which just produced another error:
Checking plugin class 'ggConvolv'... passed.
Generating testbench file 'testbench_ggConvolv.m'... done.
Running testbench... passed.
Generating mex file 'testbench_ggConvolv_mex.mexmaci64'... Failed to compute constant value for nontunable property 'pNumPartitions'. In code generation, nontunable properties can only be assigned constant values.
Error in ==> ggConvolv Line: 46 Column: 13
Code generation failed: View Error Report
Error using coder.internal.generateAudioPlugin
Error in validateAudioPlugin
Here is the current state of the plugin code:
classdef (StrictDefaults)ggConvolv < audioPlugin & matlab.System
properties(Nontunable)
concatenatedTdis44 = audioread('concatenatedTdis44.wav')';
concatenatedTdis48 = audioread('concatenatedTdis48.wav')';
PartitionSize = 1024;
end
properties (Constant, Hidden)
PluginInterface = audioPluginInterface(...
'InputChannels',1,...
'OutputChannels',1,...
'PluginName','GainGuardian'...
);
end
properties(Access = private, Nontunable)
pFIR
tdiMatrix
currentTDIIndex = 1;
sampleCounter = 0;
switchSamples % Number of samples to wait before switching
Fs
N
end
methods(Access = protected)
function setupImpl(plugin, u)
plugin.Fs = getSampleRate(plugin);
plugin.N = plugin.Fs / 3;
if plugin.Fs == 44100
concatenatedTdis = plugin.concatenatedTdis44;
elseif plugin.Fs == 48000
concatenatedTdis = plugin.concatenatedTdis48;
end
totalTdis = length(concatenatedTdis) / plugin.N;
plugin.tdiMatrix = reshape(concatenatedTdis, plugin.N, totalTdis)';
firstTdi = plugin.tdiMatrix(1,:);
plugin.pFIR = dsp.FrequencyDomainFIRFilter('Numerator', firstTdi, ...
'PartitionForReducedLatency', true, 'PartitionLength', plugin.PartitionSize);
setup(plugin.pFIR, u);
plugin.switchSamples = floor(11e-3 * getSampleRate(plugin)); % 11 milliseconds
plugin.sampleCounter = 0;
plugin.currentTDIIndex = 1;
end
function y = stepImpl(plugin,u)
plugin.sampleCounter = plugin.sampleCounter + size(u,1);
if plugin.sampleCounter >= plugin.switchSamples
plugin.sampleCounter = mod(plugin.sampleCounter, plugin.switchSamples);
plugin.currentTDIIndex = mod(plugin.currentTDIIndex, size(plugin.tdiMatrix, 1)) + 1;
plugin.pFIR.Numerator = plugin.tdiMatrix(plugin.currentTDIIndex, :);
end
y = step(plugin.pFIR,u);
end
function resetImpl(plugin)
reset(plugin.pFIR);
setLatencyInSamples(plugin, plugin.PartitionSize);
end
function flag = isInputSizeMutableImpl(~,~)
flag = true;
end
function s = saveObjectImpl(obj)
s = saveObjectImpl@matlab.System(obj);
s = savePluginProps(obj,s);
if isLocked(obj)
s.pFIR = matlab.System.saveObject(obj.pFIR);
end
end
function loadObjectImpl(obj, s, wasLocked)
if wasLocked
obj.pFIR = matlab.System.loadObject(s.pFIR);
end
loadObjectImpl@matlab.System(obj,s,wasLocked);
reload(obj,s);
end
%------------------------------------------------------------------
% Propagators
function varargout = isOutputComplexImpl(~)
varargout{1} = false;
end
function varargout = getOutputSizeImpl(obj)
varargout{1} = propagatedInputSize(obj, 1);
end
function varargout = getOutputDataTypeImpl(obj)
varargout{1} = propagatedInputDataType(obj, 1);
end
function varargout = isOutputFixedSizeImpl(obj)
varargout{1} = propagatedInputFixedSize(obj,1);
end
end
end
  4 Comments
Nathan Lively
Nathan Lively on 26 Feb 2024
Ah, maybe the impulse response must be divisible by the PartitionSize? But then, how would FastConvolver accept any wav file??

Sign in to comment.

Accepted Answer

jibrahim
jibrahim on 26 Feb 2024
Hi Nathan,
The main issue was that the code is using different filters and numerator sizes based on getSampleRate. One of the fundamental properties of audio plugins is that the sample rate is tunable. The sample rate is unknown at the time when you are generating code, and therefore you cannot rely on tnhat value to set resources such as state dimensions.
In the code below, I created separate sub-objects and local functions to handle the two sample rates of 48K and 44.1K. The generated plugin would work for either sample rate.
I also changed some properties from non-tunable to tunable because their values were being modified in stepImpl.
I am sure some of the details in the code are off and will need to be changed to work for your particular use case, but the plugin does at least pass vailidation and generation.
To test the plugin in MATLAB for different sample rates:
g = ggConvolv;
setSampleRate(g,48000)
y = g(randn(1024,1); % test for 48 k)
g = ggConvolv;
setSampleRate(g,44100)
y = g(randn(1024,1); % test for 44.1 k)
To validate and generate the plugin:
validateAudioPlugin ggConvolv
generateAudioPlugin ggConvolv
The code:
classdef (StrictDefaults)ggConvolv < audioPlugin & matlab.System
properties(Nontunable)
concatenatedTdis44 = randn(1,14700*100); % placeholder value
concatenatedTdis48 = randn(1,16000*100); % placeholder value
PartitionSize = 1024;
end
properties (Constant, Hidden)
PluginInterface = audioPluginInterface(...
'InputChannels',1,...
'OutputChannels',1,...
'PluginName','GainGuardian'...
);
end
properties(Access = private, Nontunable)
% Split objects - one set for each Fs
pFIR44
pFIR48
tdiMatrix44
tdiMatrix48
end
properties(Access = private)
sampleCounter = 0;
currentTDIIndex44 = 1;
currentTDIIndex48 = 1;
switchSamples % Number of samples to wait before switching
end
methods(Access = protected)
function setupImpl(plugin, u)
% 44100
N = 44100 / 3;
totalTdis = length(plugin.concatenatedTdis44) / N;
plugin.tdiMatrix44 = reshape(plugin.concatenatedTdis44, N, totalTdis)';
firstTdi = plugin.tdiMatrix44(1,:);
plugin.pFIR44 = dsp.FrequencyDomainFIRFilter('Numerator', firstTdi, ...
'PartitionForReducedLatency', true, 'PartitionLength', plugin.PartitionSize);
setup(plugin.pFIR44, u);
plugin.currentTDIIndex44 = 1;
% 48000
N = 48000 / 3;
totalTdis = length(plugin.concatenatedTdis48) / N;
plugin.tdiMatrix48 = reshape( plugin.concatenatedTdis48, N, totalTdis)';
firstTdi = plugin.tdiMatrix48(1,:);
plugin.pFIR48 = dsp.FrequencyDomainFIRFilter('Numerator', firstTdi, ...
'PartitionForReducedLatency', true, 'PartitionLength', plugin.PartitionSize);
setup(plugin.pFIR48, u);
plugin.currentTDIIndex48 = 1;
Fs = getSampleRate(plugin);
plugin.switchSamples = floor(11e-3 * Fs); % 11 milliseconds
plugin.sampleCounter = 0;
end
function y = step44(plugin,u)
M = plugin.tdiMatrix44;
filt = plugin.pFIR44;
currIdx = plugin.currentTDIIndex44;
plugin.sampleCounter = plugin.sampleCounter + size(u,1);
if plugin.sampleCounter >= plugin.switchSamples
plugin.sampleCounter = mod(plugin.sampleCounter, plugin.switchSamples);
currIdx = mod(currIdx, size(M, 1)) + 1;
filt.Numerator = M(currIdx, 1:size(filt.Numerator,2));
end
y = step(filt,u);
plugin.currentTDIIndex44 = currIdx;
end
function y = step48(plugin,u)
M = plugin.tdiMatrix48;
filt = plugin.pFIR48;
currIdx = plugin.currentTDIIndex48;
plugin.sampleCounter = plugin.sampleCounter + size(u,1);
if plugin.sampleCounter >= plugin.switchSamples
plugin.sampleCounter = mod(plugin.sampleCounter, plugin.switchSamples);
currIdx = mod(currIdx, size(M, 1)) + 1;
filt.Numerator = M(currIdx, 1:size(filt.Numerator,2));
end
y = step(filt,u);
plugin.currentTDIIndex48 = currIdx;
end
function y = stepImpl(plugin,u)
Fs = getSampleRate(plugin);
switch Fs
case 44100
y = step44(plugin,u);
case 48000
y = step48(plugin,u);
otherwise
y = u;
return;
end
end
function resetImpl(plugin)
reset(plugin.pFIR44);
reset(plugin.pFIR48);
Fs = getSampleRate(plugin);
plugin.switchSamples = floor(11e-3 * Fs); % 11 milliseconds
plugin.sampleCounter = 0;
setLatencyInSamples(plugin, plugin.PartitionSize);
end
function flag = isInputSizeMutableImpl(~,~)
flag = true;
end
%------------------------------------------------------------------
% Propagators
function varargout = isOutputComplexImpl(~)
varargout{1} = false;
end
function varargout = getOutputSizeImpl(obj)
varargout{1} = propagatedInputSize(obj, 1);
end
function varargout = getOutputDataTypeImpl(obj)
varargout{1} = propagatedInputDataType(obj, 1);
end
function varargout = isOutputFixedSizeImpl(obj)
varargout{1} = propagatedInputFixedSize(obj,1);
end
end
end
  4 Comments
Nathan Lively
Nathan Lively on 28 Feb 2024
Hey @jibrahim may I ask one more question? Here we are prepared to handle two sample rates, 44.1 and 48kHz. Do we need to be able to handle any sample rate? If so, is can we modify this code to handle it? For example, since I am now generating the IR programmatically, can we create the matrix of IRs once at beinning in setup and then create them again any time there is a change in sample rate? Or is it a better idea to create the matrix if IRs once at the begining for every possible sample rate?
Thanks!
Nathan Lively
Nathan Lively on 28 Feb 2024
Actually, now that I think about it. I really only need to handle three sample rates, so it's not that big a deal to just handle them during setup.

Sign in to comment.

More Answers (0)

Categories

Find more on Audio Plugin Creation and Hosting in Help Center and File Exchange

Products


Release

R2023b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!