Example of adding a new knowledge source¶
The following is a step-by-step tutorial on how to implement a new knowledge
source to be used within the blackboard
architecture. As an example, the implementation
of a knowledge source that performs a simple
localisation task is considered here. The knowledge source will take interaural time differences computed by the
Auditory front-end as inputs and produce a hypothesis about the mostly likely position of a
sound source which will be put on the blackboard. The first step of the
implementation is to set-up a class SimpleLocalisationKS
, including a
constructor and a destructor method and two additional methods canExecute
and execute
. Certain constraints on the execution of a specific
knowledge source have to be specified in the
canExecute
method. The execute
method contains the actual algorithm
which will be computed if the knowledge source is
executed by the blackboard scheduler. As the processing for this example class
is dependent on data from the Auditory front-end as inputs, it has to be inherited from the
AuditoryFrontEndDepKS
superclass:
classdef SimpleLocalisationKS < AuditoryFrontEndDepKS
% This is the basic skeleton structure to be used for setting
% up a new KS class.
properties (SetAccess = private)
% Properties of the class can be specified here
end
methods
function obj = SimpleLocalisationKS()
% This is the class constructor. Parameters of the KS
% can be initialized here.
end
function delete(obj)
% Class destructor for cleaning up after processing.
% This function is optional.
end
function [bExecute, bWait] = canExecute(obj)
% Put constraints here that prevent the KS from being
% executed by the scheduler. Otherwise just put
bExecute = true; % KS can always be executed
bWait = false;
end
function execute(obj)
% Put algorithm here
end
end
end
After the basic structure of the class has been set up, the individual functions can be implemented. In this example, a simple localisation algorithm based on a geometric model of the head is considered. The parameters that are needed for the computation are the distance between the two microphones, the speed of sound, and the size of one signal block in seconds. These values can be specified within the class properties as follows:
properties (SetAccess = private)
micDistance; % Microphone distance in meters
speedOfSound; % Speed of sound in m/s
blockSizeSec; % Size of one audio block in seconds
end
These parameters can either be specified by the user in the class constructor, or can be transferred to the class constructor as input arguments. Additionally, the class constructor must contain a specification of the parameters that should be used for data processing within the Auditory front-end, to compute the corresponding ITD values from the available binaural audio data:
function obj = SimpleLocalisationKS(micDistance)
% Class constructor for the example KS.
% Assign specified microphone distance to class properties
obj.micDistance = micDistance;
% For simplicity, block size and speed of sound will be
% hard-coded here
obj.blockSizeSec = 0.1;
obj.speedOfSound = 343;
% Generate parameter structure for the AFE
afeParams = genParStruct( ...
'fb_type', 'gammatone', ...
'fb_lowFreqHz', 80, ...
'fb_highFreqHz', 8000, ...
'fb_nChannels', 32, ...
'cc_wSizeSec', 20E-3, ...
'cc_hSizeSec', 10E-3);
% Specify requested signal features
requests{1}.name = 'itd';
requests{1}.params = param;
% Initialize AFE
obj = obj@AuditoryFrontEndDepKS(requests);
end
The knowledge source is now able to compute ITDs
from incoming binaural audio signals and make this data available for further
processing within the class. The actual processing happens in the execute
method, which can contain arbitrary algorithms and processing steps. In this
case, using simple geometric mapping from ITDs to azimuth locations, the
execute
method can be specified as:
function execute(obj)
% Get ITDs from the AFE
afeData = obj.getAFEdata();
itdObj = afeData(1);
itds = itdObj.getSignalBlock(obj.blockSizeSec, obj.timeSinceTrigger)';
% Compute average ITD value over time and frequency
avgItd = mean(mean(itds));
% Map ITD to position
azimuth = asind(avgItd * obj.speedOfSound / obj.micDistance);
% Generate a new location hypothesis and put it on the blackboard.
% For simplicity, head orientation is assumed to be fixed to 0°
% in this case.
headOrientation = 0;
aziHyp = SourcesAzimuthsDistributionHypothesis(headOrientation, azimuth, 1);
obj.blackboard.addData('sourcesAzimuthsDistributionHypotheses', ...
aziHyp, false, obj.trigger.tmIdx);
% If desired show some debugging output, if Blackboard is started in
% verbose mode with BlackboardSystem(1);
bbprintf(obj, ['[SimpleLocalisationKS:] Created ', ...
'SourcesAzimuthsDistributionHypothesis.\n'])
% Trigger the event that is KS has been executed
notify(obj, 'KsFiredEvent', BlackboardEventData(obj.trigger.tmIdx));
end
The class destructor and the canExecute
method will be left unchanged in
this example. An explanation of how these methods work in detail can be found in
the blackboard architecture section. Having
defined all four methods as described above, the knowledge source is now ready to be used within the Blackboard system.