HARK音響ライブラリ開発チーム向けの仕様
========================================

C++ 版
--------

ノード定義の方法
^^^^^^^^^^^^^^^^

新たにノードを定義する場合、ノード定義クラスを実装する。

具体的には、以下の手順による

1. hark-lib/include/hark-lib/node にクラス定義を置く。
2. hark-lib/src/node にクラス実装を置く。
3. ソースビルドの手順を再度踏む。

クラス定義の例
"""""""""""""""

以下に、クラス定義の例を示す。

.. code-block:: c++
   :linenos:

   class MultiFFT: public NodeDef
   {
   public:
       MultiFFT();
       virtual bool operator () (size_t count, InputDataBundle &input, OutputDataBundle &output);

   private:
       void initialize_parameters(InputDataBundle &input);

   private:
       enum WINDOW_TYPE {
           UNKNOWN_WINDOW, CONJ_WINDOW, RECTANGLE_WINDOW, HAMMING_WINDOW, HANNING_WINDOW
       };

       int _length;
       WINDOW_TYPE _window_type;

       int _pslength;
       // int r;

       std::vector<float>_window;
       rfft<float>_rfft;

   };

これは、``MultiFFT`` ノードの実際の定義部分である。

1. ノードを定義するには、``NodeDef`` （あるいは名前空間を含め ``hark::NodeDef`` ）から派生したクラスを定義する。上記の例では ``MultiFFT`` クラスを定義している。
2. クラス定義には、既定のコンストラクタ、および、仮想メソッド ``operator ()`` 、 ``reset()`` が定義されていなければならない。前者はノードの入出力端子について、後者は実際の処理について定義するために用いられる。
3. それ以外のメソッドおよびメンバは、ノードの処理に応じてユーザが適宜定義することができる。

クラス実装の例
"""""""""""""""

前項に基づいたクラス実装の例を以下に示す：

コンストラクタについては、以下のような実装となる。

.. code-block:: c++
   :linenos:

   MultiFFT::MultiFFT():
       NodeDef("MultiFFT")
   {
       *this
           << Terminal::in("INPUT")
           << Terminal::in("LENGTH").def(512)
           << Terminal::in("WINDOW").def("CONJ")
           << Terminal::in("WINDOW_LENGTH").def(512)
           >> Terminal::out("OUTPUT");
   }

コンストラクタでは、親クラス ``NodeDef`` の構築時に自身の名称を定義するとともに、ノードの入出力端子について定義する。

ここでは、 ``INPUT``, ``LENGTH``, ``WINDOW``, ``WINDOW_LENGTH`` という名前の入力端子と、 ``OUTPUT`` という名前の出力端子が存在する旨定義されている。
また、 ``LENGTH``, ``WINDOW``, ``WINDOW_LENGTH`` には、それぞれ既定値が ``512``, ``"CONJ"``, ``512`` である旨の定義がされている。
``INPUT`` については既定値の定義がされていない。ユーザが陽に値を指定することを求めるパラメータ、あるいが、上位ノードからのデータを入力する端子の場合、通常既定値を定義しない。

続いて、メソッド ``operator ()`` について説明する。

メソッド ``operator ()`` は、以下の3つの引数を持つ仮想関数である。

1. 引数 ``count`` (``int``)： このメソッドは、HARK音響ライブラリのフレームワークから呼び出されるが、その呼び出しの通し番号（zero-origin）が ``count`` によって与えられる。
2. 引数 ``input`` (``hark::InputDataBundle &``)：ノードに入力されたデータは、引数 ``input`` を通じて参照する。
3. 引数 ``output`` (``hark::OutputDataBundle &``)：ノードから出力するデータは、引数 ``output`` を通じて設定する。

以下、実例を用いてそれぞれの使用方法等を説明する。

.. code-block:: c++
   :linenos:

   bool
   MultiFFT::operator () (size_t count, InputDataBundle &input, OutputDataBundle &output)
   {
       if(count == 0){
           this->initialize_parameters(input);
       }

       data inputData = input["INPUT"].last();
       data outputData;

       if(inputData.is_type<Matrix<float>>()){
           const auto &inputMatrix = inputData.cast<Matrix<float>>();
           outputData = data::create<Matrix<std::complex<float>>>(inputMatrix.rows(), this->_pslength);
           auto &outputMatrix = outputData.cast<Matrix<std::complex<float>>>();

           size_t channel_count = inputMatrix.rows();
           if(inputMatrix.cols() != this->_length){
               std::stringstream ss;
               ss << "MultiFFT: FFT length (" << this->_length << ") and column number of input matrix (" << inputMatrix.cols() << ") mismatch";
               throw RuntimeError(ss.str());
           }

           auto &fft = this->_rfft;
           for(int m = 0; m < inputMatrix.rows(); m++){
               for(int i = 0; i < inputMatrix.cols(); i++){
                   fft.a[i] = inputMatrix(m, i) * this->_window[i];
               }
               fft.analyze();
               for(int k = 0; k < outputMatrix.cols(); k++){
                   outputMatrix(m, k) = fft.A[k];
               }
           }

       }else if(inputData.is_type<std::map<int, data>>()){
           const auto &inputMap = inputData.cast<std::map<int, data>>();
           outputData = data::create<std::map<int, data>>();
           auto &outputMap = outputData.cast<std::map<int, data>>();

           // std::cout << "MultiFFT: count=" << count << ", input=" << inputMap << std::endl;

           auto &fft = this->_rfft;
           for(auto it: inputMap){
               const auto &x = it.second.cast<std::vector<float>>();
               data yData = data::create<std::vector<std::complex<float>>>(fft.flength(), .0f);
               auto &y = yData.cast<std::vector<std::complex<float>>>();

               for(int i = 0; i < x.size(); i++){
                   fft.a[i] = x[i] * this->_window[i];
               }
               fft.analyze();
               for(int k = 0; k < y.size(); k++){
                   y[k] = fft.A[k];
               }

               outputMap[it.first] = yData;
           }

       }else{
           std::stringstream ss;
           ss << "MultiFFT: Unexpected input data type";
           throw RuntimeError(ss.str());

       }

       output["OUTPUT"].push(outputData);
       return true;
   }

引数 ``count`` は、このメソッドが呼ばれた回数を表している。
従って、``count == 0`` の場合に初期化処理を行うことが可能である。
上記の例では、``count == 0`` の場合にメソッド ``initialize_parameters()`` を呼び出して初期化を実行している。

引数 ``input`` は、このノードに入力されたデータを参照するために用いる。
上記の例では、``input["INPUT"]`` として参照されている箇所がある。ここで、``"INPUT"`` という名前は、このクラスのコンストラクタで定義した入力のうちの一つを表すものである。
なお、上記の例では、コンストラクタで定義した入力のうち、``"INPUT"`` 以外が参照されていないが、実際の実装では初期化を行うメソッド ``initialize_parameters()`` の内部で参照されている。

ところで、``input[]`` が返す値は、``hark::data`` 型のオブジェクトである。
``hark::data`` は、データ型を抽象化した汎用型であり、実際に値を取り出す際には適宜 cast する必要がある。以下に、この cast 処理に関係するメソッドを説明する。

1. ``data::is_type<T>()``：``data`` オブジェクトが格納している実体が型 ``T`` であるか否かを返す。
2. ``data::cast<T>()``：``data`` オブジェクトが格納している実体が型 ``T`` である場合に、型 ``T`` への参照を返す。型 ``T`` は格納されている型と一致していなければならない。
3. ``data::as<T>()``：``data`` オブジェクトが格納している実体が型 ``T`` あるいは型 ``T`` に cast 可能である場合に、型 ``T`` の値を返す。なお、現在の実装では、数値型が格納されている場合に、その相互間の cast を行う。

引数 ``output`` は、このノードから出力するデータを設定するために用いる。
上記の例では、 ``output["OUTPUT"]`` として参照されている箇所がある。ここで、 ``"OUTPUT"`` という名前は、このクラスのコンストラクタで定義した出力のうちの一つを表すものである。

ところで、 ``output[]`` には、 ``push()`` メソッドを通じて ``hark::data`` 型のオブジェクトを設定することで、データの出力を行う。
``hark::data`` 型オブジェクトを構築するには、以下のような方法がある。

1. ``data::create<T>(...)`` メソッド：型 ``T`` のオブジェクトを新たに構築して、 ``data`` 型オブジェクトとして返す。このメソッドに与えた引数は、型 ``T`` のコンストラクタに渡される。
2. ``data<T>(const T&)`` コンストラクタ：構築済みの型 ``T`` のオブジェクトへの参照を与え、その複製を格納した ``data`` 型オブジェクトを構築する。

以上より、``operator ()`` メソッドの実装は、大まかに以下のような流れとなる。

1. ``input`` を通じて、必要なデータを取得する。
2. 適宜処理を行う。
3. ``output`` を通じて、生成されたデータを出力する。

メソッド ``reset()`` について説明する。

メソッド ``reset()`` は、ノードの初期化を行うメソッドである。
ここでいう初期化とは、パラメータに従って領域などを確保する、初回処理を指すものではなく、各種ノード内部のパラメータを初期化することを指す。

ノードを実装するにあたっては、以下を満たしてなければならない。

* ``reset()`` を呼び出した直後は、内部で確保されるているメモリ等のリソースは最小限のものでなければならない。
* ``reset()`` の呼び出しは任意のタイミングでなされるため、内部状態によらず処理が正しく行われなければならない。
* ``reset()`` の呼び出しがなされた後、初回の ``operator()`` 呼び出しは、 ``count`` が ``0`` であることを想定してよい。

Python 版
--------

ノード定義の方法
^^^^^^^^^^^^^^^^

Python 版は、C++ 版のラッパー的機能として実装されているため、Python 版において新たなノードを実装する場合には、
先に C++ 版においてノードを実装する必要がある。（ :ref:`C++ 版` 参照）

C++ 版にて実装されたノードは、Python 版ソースコードの以下の箇所を修正することで、Python 版から利用することが可能となる。

1. hark-lib/python/harklib/hark.cc の 290 行目付近の以下の記述に、他に倣ってノード名を追加する

.. code-block:: c++
   :linenos:

    DEFINE_NODEDEF(AudioStreamFromWave);
    DEFINE_NODEDEF(AudioStreamFromMemory);
    DEFINE_NODEDEF(MultiFFT);
    DEFINE_NODEDEF(FeatureRemover);
    DEFINE_NODEDEF(Delta);
    DEFINE_NODEDEF(MelFilterBank);
    DEFINE_NODEDEF(MSLSExtraction);
    DEFINE_NODEDEF(PreEmphasis);
    DEFINE_NODEDEF(Constant);
    DEFINE_NODEDEF(SemiBlindICA);
    DEFINE_NODEDEF(SpeechRecognitionClient);
    DEFINE_NODEDEF(SpectralMeanNormalizationIncremental);
    DEFINE_NODEDEF(VADZC);
    DEFINE_NODEDEF(SaveFeatures);
    DEFINE_NODEDEF(SaveWavePCM2);

2. hark-lib/python/hark/node/__init__.py の冒頭にある ``_nodes`` にノード名を追加する

.. code-block:: python
   :linenos:

    _nodes = (
        'AudioStreamFromMemory',
        'AudioStreamFromWave',
        'MultiFFT',
        'FeatureRemover',
        'Delta',
        'MelFilterBank',
        'MSLSExtraction',
        'PreEmphasis',
        'Constant',
        'SemiBlindICA',
        'SpeechRecognitionClient',
        'SpectralMeanNormalizationIncremental',
        'VADZC',
        'SaveFeatures',
        'SaveWavePCM2',
    )

3. その後、Python 版のビルドとインストールを再度行う。
