MLECO-3075: Add KWS use case API

Removed some of the templates for feature calculation that we are unlikely to ever use.
We might be able to refactor the feature caching and feature calculator code in the future
to better integrate it with with PreProcess API.

Signed-off-by: Richard Burton <richard.burton@arm.com>
Change-Id: Ic0c0c581c71e2553d41ff72cd1ed3b3efa64fa92
diff --git a/source/use_case/img_class/include/ImgClassProcessing.hpp b/source/use_case/img_class/include/ImgClassProcessing.hpp
index 5a59b5f..59db4a5 100644
--- a/source/use_case/img_class/include/ImgClassProcessing.hpp
+++ b/source/use_case/img_class/include/ImgClassProcessing.hpp
@@ -32,8 +32,19 @@
     class ImgClassPreProcess : public BasePreProcess {
 
     public:
+        /**
+         * @brief       Constructor
+         * @param[in]   model   Pointer to the the Image classification Model object.
+         **/
         explicit ImgClassPreProcess(Model* model);
 
+        /**
+         * @brief       Should perform pre-processing of 'raw' input image data and load it into
+         *              TFLite Micro input tensors ready for inference
+         * @param[in]   input      Pointer to the data that pre-processing will work on.
+         * @param[in]   inputSize  Size of the input data.
+         * @return      true if successful, false otherwise.
+         **/
         bool DoPreProcess(const void* input, size_t inputSize) override;
     };
 
@@ -50,10 +61,22 @@
         std::vector<ClassificationResult>& m_results;
 
     public:
+        /**
+         * @brief       Constructor
+         * @param[in]   classifier   Classifier object used to get top N results from classification.
+         * @param[in]   model        Pointer to the the Image classification Model object.
+         * @param[in]   labels       Vector of string labels to identify each output of the model.
+         * @param[in]   results      Vector of classification results to store decoded outputs.
+         **/
         ImgClassPostProcess(Classifier& classifier, Model* model,
                             const std::vector<std::string>& labels,
                             std::vector<ClassificationResult>& results);
 
+        /**
+         * @brief       Should perform post-processing of the result of inference then populate
+         *              populate classification result data for any later use.
+         * @return      true if successful, false otherwise.
+         **/
         bool DoPostProcess() override;
     };
 
diff --git a/source/use_case/img_class/src/ImgClassProcessing.cc b/source/use_case/img_class/src/ImgClassProcessing.cc
index e33e3c1..6ba88ad 100644
--- a/source/use_case/img_class/src/ImgClassProcessing.cc
+++ b/source/use_case/img_class/src/ImgClassProcessing.cc
@@ -23,6 +23,9 @@
 
     ImgClassPreProcess::ImgClassPreProcess(Model* model)
     {
+        if (!model->IsInited()) {
+            printf_err("Model is not initialised!.\n");
+        }
         this->m_model = model;
     }
 
@@ -35,7 +38,7 @@
         auto input = static_cast<const uint8_t*>(data);
         TfLiteTensor* inputTensor = this->m_model->GetInputTensor(0);
 
-        memcpy(inputTensor->data.data, input, inputSize);
+        std::memcpy(inputTensor->data.data, input, inputSize);
         debug("Input tensor populated \n");
 
         if (this->m_model->IsDataSigned()) {
@@ -52,6 +55,9 @@
              m_labels{labels},
              m_results{results}
     {
+        if (!model->IsInited()) {
+            printf_err("Model is not initialised!.\n");
+        }
         this->m_model = model;
     }
 
diff --git a/source/use_case/img_class/src/UseCaseHandler.cc b/source/use_case/img_class/src/UseCaseHandler.cc
index 98e2b59..11a1aa8 100644
--- a/source/use_case/img_class/src/UseCaseHandler.cc
+++ b/source/use_case/img_class/src/UseCaseHandler.cc
@@ -37,6 +37,12 @@
     {
         auto& profiler = ctx.Get<Profiler&>("profiler");
         auto& model = ctx.Get<Model&>("model");
+        /* If the request has a valid size, set the image index as it might not be set. */
+        if (imgIndex < NUMBER_OF_FILES) {
+            if (!SetAppCtxIfmIdx(ctx, imgIndex, "imgIndex")) {
+                return false;
+            }
+        }
         auto initialImIdx = ctx.Get<uint32_t>("imgIndex");
 
         constexpr uint32_t dataPsnImgDownscaleFactor = 2;
@@ -46,12 +52,7 @@
         constexpr uint32_t dataPsnTxtInfStartX = 150;
         constexpr uint32_t dataPsnTxtInfStartY = 40;
 
-        /* If the request has a valid size, set the image index. */
-        if (imgIndex < NUMBER_OF_FILES) {
-            if (!SetAppCtxIfmIdx(ctx, imgIndex, "imgIndex")) {
-                return false;
-            }
-        }
+
         if (!model.IsInited()) {
             printf_err("Model is not initialised! Terminating processing.\n");
             return false;
@@ -102,7 +103,7 @@
 
             /* Display message on the LCD - inference running. */
             hal_lcd_display_text(str_inf.c_str(), str_inf.size(),
-                                    dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
+                    dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
 
             /* Select the image to run inference with. */
             info("Running inference on image %" PRIu32 " => %s\n", ctx.Get<uint32_t>("imgIndex"),
@@ -129,7 +130,7 @@
             /* Erase. */
             str_inf = std::string(str_inf.size(), ' ');
             hal_lcd_display_text(str_inf.c_str(), str_inf.size(),
-                                    dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
+                    dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
 
             /* Add results to context for access outside handler. */
             ctx.Set<std::vector<ClassificationResult>>("results", results);
diff --git a/source/use_case/kws/include/KwsProcessing.hpp b/source/use_case/kws/include/KwsProcessing.hpp
new file mode 100644
index 0000000..abf20ab
--- /dev/null
+++ b/source/use_case/kws/include/KwsProcessing.hpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2022 Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef KWS_PROCESSING_HPP
+#define KWS_PROCESSING_HPP
+
+#include <AudioUtils.hpp>
+#include "BaseProcessing.hpp"
+#include "Model.hpp"
+#include "Classifier.hpp"
+#include "MicroNetKwsMfcc.hpp"
+
+#include <functional>
+
+namespace arm {
+namespace app {
+
+    /**
+     * @brief   Pre-processing class for Keyword Spotting use case.
+     *          Implements methods declared by BasePreProcess and anything else needed
+     *          to populate input tensors ready for inference.
+     */
+    class KWSPreProcess : public BasePreProcess {
+
+    public:
+        /**
+         * @brief       Constructor
+         * @param[in]   model             Pointer to the the KWS Model object.
+         * @param[in]   numFeatures       How many MFCC features to use.
+         * @param[in]   mfccFrameLength   Number of audio samples used to calculate one set of MFCC values when
+         *                                sliding a window through the audio sample.
+         * @param[in]   mfccFrameStride   Number of audio samples between consecutive windows.
+         **/
+        explicit KWSPreProcess(Model* model, size_t numFeatures, int mfccFrameLength, int mfccFrameStride);
+
+        /**
+         * @brief       Should perform pre-processing of 'raw' input audio data and load it into
+         *              TFLite Micro input tensors ready for inference.
+         * @param[in]   input      Pointer to the data that pre-processing will work on.
+         * @param[in]   inputSize  Size of the input data.
+         * @return      true if successful, false otherwise.
+         **/
+        bool DoPreProcess(const void* input, size_t inputSize) override;
+
+        size_t m_audioWindowIndex = 0;  /* Index of audio slider, used when caching features in longer clips. */
+        size_t m_audioDataWindowSize;   /* Amount of audio needed for 1 inference. */
+        size_t m_audioDataStride;       /* Amount of audio to stride across if doing >1 inference in longer clips. */
+
+    private:
+        const int m_mfccFrameLength;
+        const int m_mfccFrameStride;
+
+        audio::MicroNetKwsMFCC m_mfcc;
+        audio::SlidingWindow<const int16_t> m_mfccSlidingWindow;
+        size_t m_numMfccVectorsInAudioStride;
+        size_t m_numReusedMfccVectors;
+        std::function<void (std::vector<int16_t>&, int, bool, size_t)> m_mfccFeatureCalculator;
+
+        /**
+         * @brief Returns a function to perform feature calculation and populates input tensor data with
+         * MFCC data.
+         *
+         * Input tensor data type check is performed to choose correct MFCC feature data type.
+         * If tensor has an integer data type then original features are quantised.
+         *
+         * Warning: MFCC calculator provided as input must have the same life scope as returned function.
+         *
+         * @param[in]       mfcc          MFCC feature calculator.
+         * @param[in,out]   inputTensor   Input tensor pointer to store calculated features.
+         * @param[in]       cacheSize     Size of the feature vectors cache (number of feature vectors).
+         * @return          Function to be called providing audio sample and sliding window index.
+         */
+        std::function<void (std::vector<int16_t>&, int, bool, size_t)>
+        GetFeatureCalculator(audio::MicroNetKwsMFCC&  mfcc,
+                             TfLiteTensor*            inputTensor,
+                             size_t                   cacheSize);
+
+        template<class T>
+        std::function<void (std::vector<int16_t>&, size_t, bool, size_t)>
+        FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize,
+                    std::function<std::vector<T> (std::vector<int16_t>& )> compute);
+    };
+
+    /**
+     * @brief   Post-processing class for Keyword Spotting use case.
+     *          Implements methods declared by BasePostProcess and anything else needed
+     *          to populate result vector.
+     */
+    class KWSPostProcess : public BasePostProcess {
+
+    private:
+        Classifier& m_kwsClassifier;
+        const std::vector<std::string>& m_labels;
+        std::vector<ClassificationResult>& m_results;
+
+    public:
+        const float m_scoreThreshold;
+        /**
+         * @brief       Constructor
+         * @param[in]   classifier       Classifier object used to get top N results from classification.
+         * @param[in]   model            Pointer to the the Image classification Model object.
+         * @param[in]   labels           Vector of string labels to identify each output of the model.
+         * @param[in]   results          Vector of classification results to store decoded outputs.
+         * @param[in]   scoreThreshold   Predicted model score must be larger than this value to be accepted.
+         **/
+        KWSPostProcess(Classifier& classifier, Model* model,
+                       const std::vector<std::string>& labels,
+                       std::vector<ClassificationResult>& results,
+                       float scoreThreshold);
+
+        /**
+         * @brief       Should perform post-processing of the result of inference then populate
+         *              populate KWS result data for any later use.
+         * @return      true if successful, false otherwise.
+         **/
+        bool DoPostProcess() override;
+    };
+
+} /* namespace app */
+} /* namespace arm */
+
+#endif /* KWS_PROCESSING_HPP */
\ No newline at end of file
diff --git a/source/use_case/kws/src/KwsProcessing.cc b/source/use_case/kws/src/KwsProcessing.cc
new file mode 100644
index 0000000..b6b230c
--- /dev/null
+++ b/source/use_case/kws/src/KwsProcessing.cc
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2022 Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "KwsProcessing.hpp"
+#include "ImageUtils.hpp"
+#include "log_macros.h"
+#include "MicroNetKwsModel.hpp"
+
+namespace arm {
+namespace app {
+
+    KWSPreProcess::KWSPreProcess(Model* model, size_t numFeatures, int mfccFrameLength, int mfccFrameStride):
+        m_mfccFrameLength{mfccFrameLength},
+        m_mfccFrameStride{mfccFrameStride},
+        m_mfcc{audio::MicroNetKwsMFCC(numFeatures, mfccFrameLength)}
+    {
+        if (!model->IsInited()) {
+            printf_err("Model is not initialised!.\n");
+        }
+        this->m_model = model;
+        this->m_mfcc.Init();
+
+        TfLiteIntArray* inputShape = model->GetInputShape(0);
+        const uint32_t numMfccFrames = inputShape->data[arm::app::MicroNetKwsModel::ms_inputRowsIdx];
+
+        /* Deduce the data length required for 1 inference from the network parameters. */
+        this->m_audioDataWindowSize = numMfccFrames * this->m_mfccFrameStride +
+                (this->m_mfccFrameLength - this->m_mfccFrameStride);
+
+        /* Creating an MFCC feature sliding window for the data required for 1 inference. */
+        this->m_mfccSlidingWindow = audio::SlidingWindow<const int16_t>(nullptr, this->m_audioDataWindowSize,
+                this->m_mfccFrameLength, this->m_mfccFrameStride);
+
+        /* For longer audio clips we choose to move by half the audio window size
+         * => for a 1 second window size there is an overlap of 0.5 seconds. */
+        this->m_audioDataStride = this->m_audioDataWindowSize / 2;
+
+        /* To have the previously calculated features re-usable, stride must be multiple
+         * of MFCC features window stride. Reduce stride through audio if needed. */
+        if (0 != this->m_audioDataStride % this->m_mfccFrameStride) {
+            this->m_audioDataStride -= this->m_audioDataStride % this->m_mfccFrameStride;
+        }
+
+        this->m_numMfccVectorsInAudioStride = this->m_audioDataStride / this->m_mfccFrameStride;
+
+        /* Calculate number of the feature vectors in the window overlap region.
+         * These feature vectors will be reused.*/
+        this->m_numReusedMfccVectors = this->m_mfccSlidingWindow.TotalStrides() + 1
+                - this->m_numMfccVectorsInAudioStride;
+
+        /* Construct feature calculation function. */
+        this->m_mfccFeatureCalculator = GetFeatureCalculator(this->m_mfcc, this->m_model->GetInputTensor(0),
+                                                             this->m_numReusedMfccVectors);
+
+        if (!this->m_mfccFeatureCalculator) {
+            printf_err("Feature calculator not initialized.");
+        }
+    }
+
+    bool KWSPreProcess::DoPreProcess(const void* data, size_t inputSize)
+    {
+        UNUSED(inputSize);
+        if (data == nullptr) {
+            printf_err("Data pointer is null");
+        }
+
+        /* Set the features sliding window to the new address. */
+        auto input = static_cast<const int16_t*>(data);
+        this->m_mfccSlidingWindow.Reset(input);
+
+        /* Cache is only usable if we have more than 1 inference in an audio clip. */
+        bool useCache = this->m_audioWindowIndex > 0 && this->m_numReusedMfccVectors > 0;
+
+        /* Use a sliding window to calculate MFCC features frame by frame. */
+        while (this->m_mfccSlidingWindow.HasNext()) {
+            const int16_t* mfccWindow = this->m_mfccSlidingWindow.Next();
+
+            std::vector<int16_t> mfccFrameAudioData = std::vector<int16_t>(mfccWindow,
+                    mfccWindow + this->m_mfccFrameLength);
+
+            /* Compute features for this window and write them to input tensor. */
+            this->m_mfccFeatureCalculator(mfccFrameAudioData, this->m_mfccSlidingWindow.Index(),
+                                          useCache, this->m_numMfccVectorsInAudioStride);
+        }
+
+        debug("Input tensor populated \n");
+
+        return true;
+    }
+
+    /**
+     * @brief Generic feature calculator factory.
+     *
+     * Returns lambda function to compute features using features cache.
+     * Real features math is done by a lambda function provided as a parameter.
+     * Features are written to input tensor memory.
+     *
+     * @tparam T                Feature vector type.
+     * @param[in] inputTensor   Model input tensor pointer.
+     * @param[in] cacheSize     Number of feature vectors to cache. Defined by the sliding window overlap.
+     * @param[in] compute       Features calculator function.
+     * @return                  Lambda function to compute features.
+     */
+    template<class T>
+    std::function<void (std::vector<int16_t>&, size_t, bool, size_t)>
+    KWSPreProcess::FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize,
+                std::function<std::vector<T> (std::vector<int16_t>& )> compute)
+    {
+        /* Feature cache to be captured by lambda function. */
+        static std::vector<std::vector<T>> featureCache = std::vector<std::vector<T>>(cacheSize);
+
+        return [=](std::vector<int16_t>& audioDataWindow,
+                   size_t index,
+                   bool useCache,
+                   size_t featuresOverlapIndex)
+        {
+            T* tensorData = tflite::GetTensorData<T>(inputTensor);
+            std::vector<T> features;
+
+            /* Reuse features from cache if cache is ready and sliding windows overlap.
+             * Overlap is in the beginning of sliding window with a size of a feature cache. */
+            if (useCache && index < featureCache.size()) {
+                features = std::move(featureCache[index]);
+            } else {
+                features = std::move(compute(audioDataWindow));
+            }
+            auto size = features.size();
+            auto sizeBytes = sizeof(T) * size;
+            std::memcpy(tensorData + (index * size), features.data(), sizeBytes);
+
+            /* Start renewing cache as soon iteration goes out of the windows overlap. */
+            if (index >= featuresOverlapIndex) {
+                featureCache[index - featuresOverlapIndex] = std::move(features);
+            }
+        };
+    }
+
+    template std::function<void (std::vector<int16_t>&, size_t , bool, size_t)>
+    KWSPreProcess::FeatureCalc<int8_t>(TfLiteTensor* inputTensor,
+                        size_t cacheSize,
+                        std::function<std::vector<int8_t> (std::vector<int16_t>&)> compute);
+
+    template std::function<void(std::vector<int16_t>&, size_t, bool, size_t)>
+    KWSPreProcess::FeatureCalc<float>(TfLiteTensor* inputTensor,
+                       size_t cacheSize,
+                       std::function<std::vector<float>(std::vector<int16_t>&)> compute);
+
+
+    std::function<void (std::vector<int16_t>&, int, bool, size_t)>
+    KWSPreProcess::GetFeatureCalculator(audio::MicroNetKwsMFCC& mfcc, TfLiteTensor* inputTensor, size_t cacheSize)
+    {
+        std::function<void (std::vector<int16_t>&, size_t, bool, size_t)> mfccFeatureCalc;
+
+        TfLiteQuantization quant = inputTensor->quantization;
+
+        if (kTfLiteAffineQuantization == quant.type) {
+            auto *quantParams = (TfLiteAffineQuantization *) quant.params;
+            const float quantScale = quantParams->scale->data[0];
+            const int quantOffset = quantParams->zero_point->data[0];
+
+            switch (inputTensor->type) {
+                case kTfLiteInt8: {
+                    mfccFeatureCalc = this->FeatureCalc<int8_t>(inputTensor,
+                                                          cacheSize,
+                                                          [=, &mfcc](std::vector<int16_t>& audioDataWindow) {
+                                                              return mfcc.MfccComputeQuant<int8_t>(audioDataWindow,
+                                                                                                   quantScale,
+                                                                                                   quantOffset);
+                                                          }
+                    );
+                    break;
+                }
+                default:
+                printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type));
+            }
+        } else {
+            mfccFeatureCalc = this->FeatureCalc<float>(inputTensor, cacheSize,
+                    [&mfcc](std::vector<int16_t>& audioDataWindow) {
+                return mfcc.MfccCompute(audioDataWindow); }
+                );
+        }
+        return mfccFeatureCalc;
+    }
+
+    KWSPostProcess::KWSPostProcess(Classifier& classifier, Model* model,
+                                   const std::vector<std::string>& labels,
+                                   std::vector<ClassificationResult>& results, float scoreThreshold)
+            :m_kwsClassifier{classifier},
+             m_labels{labels},
+             m_results{results},
+             m_scoreThreshold{scoreThreshold}
+    {
+        if (!model->IsInited()) {
+            printf_err("Model is not initialised!.\n");
+        }
+        this->m_model = model;
+    }
+
+    bool KWSPostProcess::DoPostProcess()
+    {
+        return this->m_kwsClassifier.GetClassificationResults(
+                this->m_model->GetOutputTensor(0), this->m_results,
+                this->m_labels, 1, true);
+    }
+
+} /* namespace app */
+} /* namespace arm */
\ No newline at end of file
diff --git a/source/use_case/kws/src/UseCaseHandler.cc b/source/use_case/kws/src/UseCaseHandler.cc
index e04cefc..350d34b 100644
--- a/source/use_case/kws/src/UseCaseHandler.cc
+++ b/source/use_case/kws/src/UseCaseHandler.cc
@@ -20,15 +20,14 @@
 #include "Classifier.hpp"
 #include "MicroNetKwsModel.hpp"
 #include "hal.h"
-#include "MicroNetKwsMfcc.hpp"
 #include "AudioUtils.hpp"
 #include "ImageUtils.hpp"
 #include "UseCaseCommonUtils.hpp"
 #include "KwsResult.hpp"
 #include "log_macros.h"
+#include "KwsProcessing.hpp"
 
 #include <vector>
-#include <functional>
 
 using KwsClassifier = arm::app::Classifier;
 
@@ -37,36 +36,27 @@
 
 
     /**
-     * @brief           Presents inference results using the data presentation
-     *                  object.
-     * @param[in]       results     Vector of classification results to be displayed.
+     * @brief           Presents KWS inference results.
+     * @param[in]       results     Vector of KWS classification results to be displayed.
      * @return          true if successful, false otherwise.
      **/
     static bool PresentInferenceResult(const std::vector<arm::app::kws::KwsResult>& results);
 
-    /**
-     * @brief Returns a function to perform feature calculation and populates input tensor data with
-     * MFCC data.
-     *
-     * Input tensor data type check is performed to choose correct MFCC feature data type.
-     * If tensor has an integer data type then original features are quantised.
-     *
-     * Warning: MFCC calculator provided as input must have the same life scope as returned function.
-     *
-     * @param[in]       mfcc          MFCC feature calculator.
-     * @param[in,out]   inputTensor   Input tensor pointer to store calculated features.
-     * @param[in]       cacheSize     Size of the feature vectors cache (number of feature vectors).
-     * @return          Function to be called providing audio sample and sliding window index.
-     */
-    static std::function<void (std::vector<int16_t>&, int, bool, size_t)>
-            GetFeatureCalculator(audio::MicroNetKwsMFCC&  mfcc,
-                                 TfLiteTensor*      inputTensor,
-                                 size_t             cacheSize);
-
-    /* Audio inference handler. */
+    /* KWS inference handler. */
     bool ClassifyAudioHandler(ApplicationContext& ctx, uint32_t clipIndex, bool runAll)
     {
         auto& profiler = ctx.Get<Profiler&>("profiler");
+        auto& model = ctx.Get<Model&>("model");
+        const auto mfccFrameLength = ctx.Get<int>("frameLength");
+        const auto mfccFrameStride = ctx.Get<int>("frameStride");
+        const auto scoreThreshold = ctx.Get<float>("scoreThreshold");
+        /* If the request has a valid size, set the audio index. */
+        if (clipIndex < NUMBER_OF_FILES) {
+            if (!SetAppCtxIfmIdx(ctx, clipIndex,"clipIndex")) {
+                return false;
+            }
+        }
+        auto initialClipIdx = ctx.Get<uint32_t>("clipIndex");
 
         constexpr uint32_t dataPsnTxtInfStartX = 20;
         constexpr uint32_t dataPsnTxtInfStartY = 40;
@@ -74,27 +64,13 @@
             (arm::app::MicroNetKwsModel::ms_inputRowsIdx > arm::app::MicroNetKwsModel::ms_inputColsIdx)?
              arm::app::MicroNetKwsModel::ms_inputRowsIdx : arm::app::MicroNetKwsModel::ms_inputColsIdx);
 
-        auto& model = ctx.Get<Model&>("model");
 
-        /* If the request has a valid size, set the audio index. */
-        if (clipIndex < NUMBER_OF_FILES) {
-            if (!SetAppCtxIfmIdx(ctx, clipIndex,"clipIndex")) {
-                return false;
-            }
-        }
         if (!model.IsInited()) {
             printf_err("Model is not initialised! Terminating processing.\n");
             return false;
         }
 
-        const auto frameLength = ctx.Get<int>("frameLength");
-        const auto frameStride = ctx.Get<int>("frameStride");
-        const auto scoreThreshold = ctx.Get<float>("scoreThreshold");
-        auto startClipIdx = ctx.Get<uint32_t>("clipIndex");
-
-        TfLiteTensor* outputTensor = model.GetOutputTensor(0);
         TfLiteTensor* inputTensor = model.GetInputTensor(0);
-
         if (!inputTensor->dims) {
             printf_err("Invalid input tensor dims\n");
             return false;
@@ -103,130 +79,89 @@
             return false;
         }
 
+        /* Get input shape for feature extraction. */
         TfLiteIntArray* inputShape = model.GetInputShape(0);
-        const uint32_t kNumCols = inputShape->data[arm::app::MicroNetKwsModel::ms_inputColsIdx];
-        const uint32_t kNumRows = inputShape->data[arm::app::MicroNetKwsModel::ms_inputRowsIdx];
-
-        audio::MicroNetKwsMFCC mfcc = audio::MicroNetKwsMFCC(kNumCols, frameLength);
-        mfcc.Init();
-
-        /* Deduce the data length required for 1 inference from the network parameters. */
-        auto audioDataWindowSize = kNumRows * frameStride + (frameLength - frameStride);
-        auto mfccWindowSize = frameLength;
-        auto mfccWindowStride = frameStride;
-
-        /* We choose to move by half the window size => for a 1 second window size
-         * there is an overlap of 0.5 seconds. */
-        auto audioDataStride = audioDataWindowSize / 2;
-
-        /* To have the previously calculated features re-usable, stride must be multiple
-         * of MFCC features window stride. */
-        if (0 != audioDataStride % mfccWindowStride) {
-
-            /* Reduce the stride. */
-            audioDataStride -= audioDataStride % mfccWindowStride;
-        }
-
-        auto nMfccVectorsInAudioStride = audioDataStride/mfccWindowStride;
+        const uint32_t numMfccFeatures = inputShape->data[arm::app::MicroNetKwsModel::ms_inputColsIdx];
 
         /* We expect to be sampling 1 second worth of data at a time.
          * NOTE: This is only used for time stamp calculation. */
-        const float secondsPerSample = 1.0/audio::MicroNetKwsMFCC::ms_defaultSamplingFreq;
+        const float secondsPerSample = 1.0 / audio::MicroNetKwsMFCC::ms_defaultSamplingFreq;
+
+        /* Set up pre and post-processing. */
+        KWSPreProcess preprocess = KWSPreProcess(&model, numMfccFeatures, mfccFrameLength, mfccFrameStride);
+
+        std::vector<ClassificationResult> singleInfResult;
+        KWSPostProcess postprocess = KWSPostProcess(ctx.Get<KwsClassifier &>("classifier"), &model,
+                                                    ctx.Get<std::vector<std::string>&>("labels"),
+                                                    singleInfResult, scoreThreshold);
+
+        UseCaseRunner runner = UseCaseRunner(&preprocess, &postprocess, &model);
 
         do {
             hal_lcd_clear(COLOR_BLACK);
 
             auto currentIndex = ctx.Get<uint32_t>("clipIndex");
 
-            /* Creating a mfcc features sliding window for the data required for 1 inference. */
-            auto audioMFCCWindowSlider = audio::SlidingWindow<const int16_t>(
-                                            get_audio_array(currentIndex),
-                                            audioDataWindowSize, mfccWindowSize,
-                                            mfccWindowStride);
-
             /* Creating a sliding window through the whole audio clip. */
             auto audioDataSlider = audio::SlidingWindow<const int16_t>(
-                                        get_audio_array(currentIndex),
-                                        get_audio_array_size(currentIndex),
-                                        audioDataWindowSize, audioDataStride);
+                    get_audio_array(currentIndex),
+                    get_audio_array_size(currentIndex),
+                    preprocess.m_audioDataWindowSize, preprocess.m_audioDataStride);
 
-            /* Calculate number of the feature vectors in the window overlap region.
-             * These feature vectors will be reused.*/
-            auto numberOfReusedFeatureVectors = audioMFCCWindowSlider.TotalStrides() + 1
-                                                - nMfccVectorsInAudioStride;
-
-            /* Construct feature calculation function. */
-            auto mfccFeatureCalc = GetFeatureCalculator(mfcc, inputTensor,
-                                                        numberOfReusedFeatureVectors);
-
-            if (!mfccFeatureCalc){
-                return false;
-            }
-
-            /* Declare a container for results. */
-            std::vector<arm::app::kws::KwsResult> results;
+            /* Declare a container to hold results from across the whole audio clip. */
+            std::vector<kws::KwsResult> finalResults;
 
             /* Display message on the LCD - inference running. */
             std::string str_inf{"Running inference... "};
-            hal_lcd_display_text(
-                                str_inf.c_str(), str_inf.size(),
-                                dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
+            hal_lcd_display_text(str_inf.c_str(), str_inf.size(),
+                    dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
             info("Running inference on audio clip %" PRIu32 " => %s\n", currentIndex,
                  get_filename(currentIndex));
 
             /* Start sliding through audio clip. */
             while (audioDataSlider.HasNext()) {
-                const int16_t *inferenceWindow = audioDataSlider.Next();
-
-                /* We moved to the next window - set the features sliding to the new address. */
-                audioMFCCWindowSlider.Reset(inferenceWindow);
+                const int16_t* inferenceWindow = audioDataSlider.Next();
 
                 /* The first window does not have cache ready. */
-                bool useCache = audioDataSlider.Index() > 0 && numberOfReusedFeatureVectors > 0;
-
-                /* Start calculating features inside one audio sliding window. */
-                while (audioMFCCWindowSlider.HasNext()) {
-                    const int16_t *mfccWindow = audioMFCCWindowSlider.Next();
-                    std::vector<int16_t> mfccAudioData = std::vector<int16_t>(mfccWindow,
-                                                            mfccWindow + mfccWindowSize);
-                    /* Compute features for this window and write them to input tensor. */
-                    mfccFeatureCalc(mfccAudioData,
-                                    audioMFCCWindowSlider.Index(),
-                                    useCache,
-                                    nMfccVectorsInAudioStride);
-                }
+                preprocess.m_audioWindowIndex = audioDataSlider.Index();
 
                 info("Inference %zu/%zu\n", audioDataSlider.Index() + 1,
                      audioDataSlider.TotalStrides() + 1);
 
-                /* Run inference over this audio clip sliding window. */
-                if (!RunInference(model, profiler)) {
+                /* Run the pre-processing, inference and post-processing. */
+                if (!runner.PreProcess(inferenceWindow, audio::MicroNetKwsMFCC::ms_defaultSamplingFreq)) {
                     return false;
                 }
 
-                std::vector<ClassificationResult> classificationResult;
-                auto& classifier = ctx.Get<KwsClassifier&>("classifier");
-                classifier.GetClassificationResults(outputTensor, classificationResult,
-                                                    ctx.Get<std::vector<std::string>&>("labels"), 1, true);
+                profiler.StartProfiling("Inference");
+                if (!runner.RunInference()) {
+                    return false;
+                }
+                profiler.StopProfiling();
 
-                results.emplace_back(kws::KwsResult(classificationResult,
-                    audioDataSlider.Index() * secondsPerSample * audioDataStride,
-                    audioDataSlider.Index(), scoreThreshold));
+                if (!runner.PostProcess()) {
+                    return false;
+                }
+
+                /* Add results from this window to our final results vector. */
+                finalResults.emplace_back(kws::KwsResult(singleInfResult,
+                        audioDataSlider.Index() * secondsPerSample * preprocess.m_audioDataStride,
+                        audioDataSlider.Index(), postprocess.m_scoreThreshold));
 
 #if VERIFY_TEST_OUTPUT
+                TfLiteTensor* outputTensor = model.GetOutputTensor(0);
                 arm::app::DumpTensor(outputTensor);
 #endif /* VERIFY_TEST_OUTPUT */
             } /* while (audioDataSlider.HasNext()) */
 
             /* Erase. */
             str_inf = std::string(str_inf.size(), ' ');
-            hal_lcd_display_text(
-                                str_inf.c_str(), str_inf.size(),
-                                dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
+            hal_lcd_display_text(str_inf.c_str(), str_inf.size(),
+                    dataPsnTxtInfStartX, dataPsnTxtInfStartY, false);
 
-            ctx.Set<std::vector<arm::app::kws::KwsResult>>("results", results);
+            ctx.Set<std::vector<kws::KwsResult>>("results", finalResults);
 
-            if (!PresentInferenceResult(results)) {
+            if (!PresentInferenceResult(finalResults)) {
                 return false;
             }
 
@@ -234,58 +169,11 @@
 
             IncrementAppCtxIfmIdx(ctx,"clipIndex");
 
-        } while (runAll && ctx.Get<uint32_t>("clipIndex") != startClipIdx);
+        } while (runAll && ctx.Get<uint32_t>("clipIndex") != initialClipIdx);
 
         return true;
     }
 
-    /**
-     * @brief Generic feature calculator factory.
-     *
-     * Returns lambda function to compute features using features cache.
-     * Real features math is done by a lambda function provided as a parameter.
-     * Features are written to input tensor memory.
-     *
-     * @tparam T                Feature vector type.
-     * @param[in] inputTensor   Model input tensor pointer.
-     * @param[in] cacheSize     Number of feature vectors to cache. Defined by the sliding window overlap.
-     * @param[in] compute       Features calculator function.
-     * @return                  Lambda function to compute features.
-     */
-    template<class T>
-    std::function<void (std::vector<int16_t>&, size_t, bool, size_t)>
-    FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize,
-                std::function<std::vector<T> (std::vector<int16_t>& )> compute)
-    {
-        /* Feature cache to be captured by lambda function. */
-        static std::vector<std::vector<T>> featureCache = std::vector<std::vector<T>>(cacheSize);
-
-        return [=](std::vector<int16_t>& audioDataWindow,
-                                     size_t index,
-                                     bool useCache,
-                                     size_t featuresOverlapIndex)
-        {
-            T *tensorData = tflite::GetTensorData<T>(inputTensor);
-            std::vector<T> features;
-
-            /* Reuse features from cache if cache is ready and sliding windows overlap.
-             * Overlap is in the beginning of sliding window with a size of a feature cache. */
-            if (useCache && index < featureCache.size()) {
-                features = std::move(featureCache[index]);
-            } else {
-                features = std::move(compute(audioDataWindow));
-            }
-            auto size = features.size();
-            auto sizeBytes = sizeof(T) * size;
-            std::memcpy(tensorData + (index * size), features.data(), sizeBytes);
-
-            /* Start renewing cache as soon iteration goes out of the windows overlap. */
-            if (index >= featuresOverlapIndex) {
-                featureCache[index - featuresOverlapIndex] = std::move(features);
-            }
-        };
-    }
-
     static bool PresentInferenceResult(const std::vector<arm::app::kws::KwsResult>& results)
     {
         constexpr uint32_t dataPsnTxtStartX1 = 20;
@@ -299,40 +187,39 @@
         /* Display each result */
         uint32_t rowIdx1 = dataPsnTxtStartY1 + 2 * dataPsnTxtYIncr;
 
-        for (uint32_t i = 0; i < results.size(); ++i) {
+        for (const auto & result : results) {
 
             std::string topKeyword{"<none>"};
             float score = 0.f;
-            if (!results[i].m_resultVec.empty()) {
-                topKeyword = results[i].m_resultVec[0].m_label;
-                score = results[i].m_resultVec[0].m_normalisedVal;
+            if (!result.m_resultVec.empty()) {
+                topKeyword = result.m_resultVec[0].m_label;
+                score = result.m_resultVec[0].m_normalisedVal;
             }
 
             std::string resultStr =
-                    std::string{"@"} + std::to_string(results[i].m_timeStamp) +
+                    std::string{"@"} + std::to_string(result.m_timeStamp) +
                     std::string{"s: "} + topKeyword + std::string{" ("} +
                     std::to_string(static_cast<int>(score * 100)) + std::string{"%)"};
 
-            hal_lcd_display_text(
-                    resultStr.c_str(), resultStr.size(),
+            hal_lcd_display_text(resultStr.c_str(), resultStr.size(),
                     dataPsnTxtStartX1, rowIdx1, false);
             rowIdx1 += dataPsnTxtYIncr;
 
-            if (results[i].m_resultVec.empty()) {
+            if (result.m_resultVec.empty()) {
                 info("For timestamp: %f (inference #: %" PRIu32
                              "); label: %s; threshold: %f\n",
-                     results[i].m_timeStamp, results[i].m_inferenceNumber,
+                     result.m_timeStamp, result.m_inferenceNumber,
                      topKeyword.c_str(),
-                     results[i].m_threshold);
+                     result.m_threshold);
             } else {
-                for (uint32_t j = 0; j < results[i].m_resultVec.size(); ++j) {
+                for (uint32_t j = 0; j < result.m_resultVec.size(); ++j) {
                     info("For timestamp: %f (inference #: %" PRIu32
                                  "); label: %s, score: %f; threshold: %f\n",
-                         results[i].m_timeStamp,
-                         results[i].m_inferenceNumber,
-                         results[i].m_resultVec[j].m_label.c_str(),
-                         results[i].m_resultVec[j].m_normalisedVal,
-                         results[i].m_threshold);
+                         result.m_timeStamp,
+                         result.m_inferenceNumber,
+                         result.m_resultVec[j].m_label.c_str(),
+                         result.m_resultVec[j].m_normalisedVal,
+                         result.m_threshold);
                 }
             }
         }
@@ -340,88 +227,5 @@
         return true;
     }
 
-    template std::function<void (std::vector<int16_t>&, size_t , bool, size_t)>
-        FeatureCalc<int8_t>(TfLiteTensor* inputTensor,
-                            size_t cacheSize,
-                            std::function<std::vector<int8_t> (std::vector<int16_t>& )> compute);
-
-    template std::function<void (std::vector<int16_t>&, size_t , bool, size_t)>
-        FeatureCalc<uint8_t>(TfLiteTensor* inputTensor,
-                             size_t cacheSize,
-                             std::function<std::vector<uint8_t> (std::vector<int16_t>& )> compute);
-
-    template std::function<void (std::vector<int16_t>&, size_t , bool, size_t)>
-        FeatureCalc<int16_t>(TfLiteTensor* inputTensor,
-                             size_t cacheSize,
-                             std::function<std::vector<int16_t> (std::vector<int16_t>& )> compute);
-
-    template std::function<void(std::vector<int16_t>&, size_t, bool, size_t)>
-        FeatureCalc<float>(TfLiteTensor* inputTensor,
-                           size_t cacheSize,
-                           std::function<std::vector<float>(std::vector<int16_t>&)> compute);
-
-
-    static std::function<void (std::vector<int16_t>&, int, bool, size_t)>
-    GetFeatureCalculator(audio::MicroNetKwsMFCC& mfcc, TfLiteTensor* inputTensor, size_t cacheSize)
-    {
-        std::function<void (std::vector<int16_t>&, size_t, bool, size_t)> mfccFeatureCalc;
-
-        TfLiteQuantization quant = inputTensor->quantization;
-
-        if (kTfLiteAffineQuantization == quant.type) {
-
-            auto *quantParams = (TfLiteAffineQuantization *) quant.params;
-            const float quantScale = quantParams->scale->data[0];
-            const int quantOffset = quantParams->zero_point->data[0];
-
-            switch (inputTensor->type) {
-                case kTfLiteInt8: {
-                    mfccFeatureCalc = FeatureCalc<int8_t>(inputTensor,
-                                                          cacheSize,
-                                                          [=, &mfcc](std::vector<int16_t>& audioDataWindow) {
-                                                              return mfcc.MfccComputeQuant<int8_t>(audioDataWindow,
-                                                                                                   quantScale,
-                                                                                                   quantOffset);
-                                                          }
-                    );
-                    break;
-                }
-                case kTfLiteUInt8: {
-                    mfccFeatureCalc = FeatureCalc<uint8_t>(inputTensor,
-                                                           cacheSize,
-                                                           [=, &mfcc](std::vector<int16_t>& audioDataWindow) {
-                                                               return mfcc.MfccComputeQuant<uint8_t>(audioDataWindow,
-                                                                                                     quantScale,
-                                                                                                     quantOffset);
-                                                           }
-                    );
-                    break;
-                }
-                case kTfLiteInt16: {
-                    mfccFeatureCalc = FeatureCalc<int16_t>(inputTensor,
-                                                           cacheSize,
-                                                           [=, &mfcc](std::vector<int16_t>& audioDataWindow) {
-                                                               return mfcc.MfccComputeQuant<int16_t>(audioDataWindow,
-                                                                                                     quantScale,
-                                                                                                     quantOffset);
-                                                           }
-                    );
-                    break;
-                }
-                default:
-                    printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type));
-            }
-
-
-        } else {
-            mfccFeatureCalc = mfccFeatureCalc = FeatureCalc<float>(inputTensor,
-                                                                   cacheSize,
-                                                                   [&mfcc](std::vector<int16_t>& audioDataWindow) {
-                                                                       return mfcc.MfccCompute(audioDataWindow);
-                                                                   });
-        }
-        return mfccFeatureCalc;
-    }
-
 } /* namespace app */
-} /* namespace arm */
\ No newline at end of file
+} /* namespace arm */
diff --git a/tests/use_case/kws/KWSHandlerTest.cc b/tests/use_case/kws/KWSHandlerTest.cc
index d0a8a3f..c24faa4 100644
--- a/tests/use_case/kws/KWSHandlerTest.cc
+++ b/tests/use_case/kws/KWSHandlerTest.cc
@@ -67,7 +67,7 @@
 
     auto checker = [&](uint32_t audioIndex, std::vector<uint32_t> labelIndex)
     {
-        caseContext.Set<uint32_t>("audioIndex", audioIndex);
+        caseContext.Set<uint32_t>("clipIndex", audioIndex);
 
         std::vector<std::string> labels;
         GetLabelsVector(labels);