===================================
iOS 版サンプルアプリケーション
===================================

サンプルアプリケーションとして配布している HARKlibSample について、その概要を説明する。

既定アプリケーションからの差分
===============================

HARKlibSample は、 Xcode で生成される iOS App プロジェクトに、
HARK音響ライブラリを使用するための処理と、サンプル用にマイクデバイスから音声を取得するための処理を追記したものである。

以下、差分について説明する。
ただし、HARK音響ライブラリに特化した部分以外の記述は簡易なものに留めている。

Storyboard を用いた UI の構築
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

UIを Main.storyboard に記述を追加することで定義する。具体的には、以下の通りである：

* ユーザが操作するための UISwitch を配置する。
* プログラムからの状態表示を目的として UITextField を配置する。
* プログラムからの出力表示を目的として UITextView を配置する。

上記 3 つの要素は、 ViewController クラスと連携して動作する。具体的には、以下の通りである：

* UISwitch を switchStatus にバインドし、操作された際の処理を、 onSwitchValueChanged メソッドに記述する。
* UITextField を _statusTextField にバインドする。
* UITextView を textView にバインドする。

なお、その他タイトル、画像、レイアウト等については割愛する。

HARKlib への参照の追加
^^^^^^^^^^^^^^^^^^^^^^^^^^^

HARKlibSample は、HARKlib を参照する。
HARKlib は Framework プロジェクトであり、その出力 Framework を参照する旨設定する。

AVFoundation によるマイク入力の取得
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

マイクからの音声入力を行うために、標準ライブラリ AVFoundation を用いる。

AVFoundation のマイク入力に関する操作は、 AudioManager クラスに記述されている。
このクラスはマイクアクセスを制御し、マイクから得られた音声データを delegate を通じてアプリケーションに返す機能を有する。

ViewController クラスのメンバとして AudioManager オブジェクトを保持している。

* ViewController.start_recognition(): マイクによる収音を開始する処理が含まれる
* ViewController.stop_recognition(): 収音を終了する処理が含まれる
* ViewController.receiveAudio(): 収音された音声データを AudioManager の機能から受け取る処理

ネットワーク定義の実装
^^^^^^^^^^^^^^^^^^^^^^^^^^^

HARK音響ライブラリの処理の定義として、ネットワークの構成を実装により定義する。
具体的には、 HARKNetworkDef クラスから派生したクラスを定義し、``build`` メソッドをオーバーライドすることとなる。

サンプルアプリケーションでは、``NetworkDefs.swift`` において、Main クラスとして実装されている。

Main クラスは以下のように定義されている。

.. code-block:: swift

    class Main: HARKNetworkDef
    {
        override func build(_ network:HARKNetwork, input:HARKDataSourceMap, output:HARKDataSinkMap)
        {
            let loop = network.create(Recording.self, name: "CAS_Recording")
        }
    }

ここで、``build`` メソッドが override されて記述されている。

``build`` メソッドには3つの引数があり、``network`` はネットワークの実体に相当するオブジェクトであり、
ネットワークの構成を定義する際にはこのオブジェクトを用いてノードの構築等を行う。
``input`` および ``output`` はネットワークの入力および出力にあたる。
ネットワークの外部とデータの送受を行う場合には、これらを用いてデータのフローを定義する。

``Main.build()`` においては、 ``network.create`` メソッドを用いて1つのノードを構築している。
具体的には、 ``Recording`` クラスによって定義されるネットワークであり、ここでは、Recordingネットワークがあたかも1つのノードであるかのごとく構築されている。
このように、ノードを組み合わせて作ったネットワークを、それが単一のノードであるかのように抽象化して扱うことができるのも、HARK音響ライブラリの特徴の一つである。

また、 ``Main.build()`` では、その構成を定義するにあたって ``input`` および ``output`` が特に参照されていない。
つまり、Main はネットワーク外部との入出力を行わない（孤立した）ネットワークである、ということになる。

続いて、Recording クラスは以下のように定義されている（一部省略）。

.. code-block:: swift

    class Recording : HARKNetworkDef
    {
        override func build(_ network:HARKNetwork, input:HARKDataSourceMap, output:HARKDataSinkMap)
        {
            //...(略)...

            let node_spectral_mean_normalization_incremental = network.create(HARKlib.SpectralMeanNormalizationIncremental.self, name: "SpectralMeanNormalizationIncremental")
            let node_audio_stream_from_memory = network.create(HARKlib.AudioStreamFromMemory.self, dispatcher: HARKlib.TriggeredMultiShotDispatcher.self, name: "AudioStreamFromMemory")
            let node_publisher = network.create(HARKlib.PublishData.self, dispatcher: HARKlib.RepeatDispatcher.self, name: "Publisher")
            let node_subscriber = network.create(HARKlib.SubscribeData.self, name: "Subscriber")

            node_audio_stream_from_memory
                .add_input("INPUT", source: node_publisher.get("OUTPUT"))

            // ...(略)...

            node_subscriber
                .add_input("INPUT", source: node_spectral_mean_normalization_incremental.get("OUTPUT"))
        }
    }

``Main.build()`` と同様に、ノードの構築を行うが、その後、ノード間の接続を定義する実装がなされている。

例えば、

.. code-block:: swift

    node_audio_stream_from_memory
        .add_input("INPUT", source: node_publisher.get("OUTPUT"))

は、ノード ``node_audio_stream_from_memory`` の ``INPUT`` という名前の入力に対し、ノード ``node_publisher`` の ``OUTPUT`` という名前の出力を接続する旨定義している。

続いて、Extracting クラスは以下の通りである（一部省略）。

.. code-block:: swift

    class Extracting : HARKNetworkDef
    {
        override func build(_ network:HARKNetwork, input:HARKDataSourceMap, output:HARKDataSinkMap)
        {
            let node_multi_fft = network.create(HARKlib.MultiFFT.self, name: "MultiFFT")
            let node_feature_remover = network.create(HARKlib.FeatureRemover.self, name: "FeatureRemover")
            // ...(略)...

            node_multi_fft
               .add_input("INPUT", source: input.get("CHANNEL_WAV"))

            node_feature_remover
               .add_input("INPUT", source: node_delta.get("OUTPUT"))
               .add_input("SELECTOR", string: "40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81")
   
            // ...(略)...

            output
                .add_input("OUTPUT", source: node_feature_remover.get("OUTPUT"))
        }
    }

これまでの ``build`` メソッドの実装と同様に、ノードの構築とその接続を定義している。

ここで、

.. code-block:: swift

    node_multi_fft
        .add_input("INPUT", source: input.get("CHANNEL_WAV"))

のように、 ``build`` メソッドの引数 ``input`` を参照し、その ``CHANNEL_WAV`` という名前の入力を参照する形となっている。
これは、このネットワークに ``CHANNEL_WAV`` という名前の入力が備わっていることを意味する。
また、その入力が ``node_multi_fft`` の ``INPUT`` という名前の入力に接続される旨の定義であることは、前述のノード間の ``add_input`` と同様である。

また、

.. code-block:: swift

    output
        .add_input("OUTPUT", source: node_feature_remover.get("OUTPUT"))

のように、 ``build`` メソッドの引数 ``output`` に対して ``add_input`` を実行することで、
このネットワークに ``OUTPUT`` という名前の出力が備わっていることを意味する。
また、この出力に ``node_feature_remover`` の ``OUTPUT`` という名前の出力が接続される旨の定義であることは、前述のノード間の ``add_input`` と同様である。

HARK音響ライブラリとのデータ送受の実装
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

HARK音響ライブラリには、その外部とのデータ送受を目的として、 PublishData および SubscribeData の２つのノードが定義されている。
サンプルアプリケーションでは、これらの２つのノードがネットワーク定義に組み込まれている。
具体的には、 ``Recording.build()`` の実装を参照されたい。

HARK音響ライブラリとアプリケーションの連携動作
===============================================

前項において、サンプルアプリケーションにおけるネットワーク定義と、データ送受の中継点としての PublishData および SubscribeData について言及した。
ここでは、これらの機能をアプリケーション側から利用する方法について説明する。

HARK音響ライブラリへのデータ送信
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

HARK音響ライブラリへのデータ送信は、PublishData 型のノードを介して行う。

ネットワーク定義において、PublishData 型ノードは、 ``Recording.build()`` にて

.. code-block:: swift

    let node_publisher = network.create(HARKlib.PublishData.self, dispatcher: HARKlib.RepeatDispatcher.self, name: "Publisher")

のように構築されている。この処理は、PublishData 型ノードを構築して、構築されたノードの実体に ``Publisher`` という名前を付することを意味する。

アプリケーション側では、 ViewController の receiveAudio メソッドにて

.. code-block:: swift

    if let publisher_node = self.harkNetwork.query_node("Publisher") as? HARKlib.PublishData {

と記述されている。
これは、構築済みのネットワーク ``self.harkNetwork`` から、 ``Publisher`` という名前のノードを検索し、得られたノード定義を ``PublishData`` 型にダウンキャストするものである。

その後、 ``publisher_node`` は、 ``MainWindow.DataAvailable()`` で参照されており、
このメソッドでは、 AudioManager から得られた音声データを16bit整数値の配列として、

.. code-block:: swift

    publisher_node.pushInt16Vector(p.baseAddress!, count: p.count)


によって PublishData ノードに送っている。

HARK音響ライブラリからのデータ受信
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

SubscribeData 型ノードは、 PublishData と逆に、HARK音響ライブラリからデータを受け取るために用いる。

アプリケーション側では、 ViewController の onDidLoad メソッドにて

.. code-block:: swift

    if let subscriber_node = self.harkNetwork.query_node("Subscriber") as? HARKlib.SubscribeData {

とネットワークから SubscribeData ノード定義を取得しており、さらに、

.. code-block:: swift

    subscriber_node.delegate = self

とすることで、HARK音響ライブラリ内部で SubscribeData 型ノードがデータを受け取った時には、
``ViewController.receive`` が呼ばれることとなる。

HARK音響ライブラリによる処理の開始
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

定義済みのネットワークを用いて、以下のようにHARK音響ライブラリの処理を開始することができる。

.. code-block:: swift

    let harkNetwork: HARKNetwork = HARKNetwork.create(Main.self, name: "MAIN")
    //...(略)...
    self.harkNetwork.execute()

ただし、 ``execute()`` はHARK音響ライブラリの処理が完了するまで制御を返さないため、実際には、

.. code-block:: swift

    self.background_queue.async {
        self.harkNetwork.execute()
    }

のように、別スレッドでの実行が想定される。

HARK音響ライブラリによる処理の終了
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

実行中のHARK音響ライブラリの処理を止めるには、

.. code-block:: swift

    self.harkNetwork.stop();

と、 ``stop()`` メソッドを用いる。
これにより、ブロックしていた ``execute()`` メソッドは制御を返す。

その他のトピック
=================

VAD 処理の切り替え
^^^^^^^^^^^^^^^^^^^

現在、 HARK音響ライブラリでは、 VAD （発話区間検出）処理を 2 通りの方法で実装している。

一つは、``VADZC`` でありもう一つは ``VADPyAnnote`` である。

先述のサンプルアプリケーションでは、 ``NetworkDef.swift`` において、配布時には ``VADZC`` を用いるようになっているが、
以下の箇所を切り替えることで、 ``VADPyAnnote`` を用いるよう変更可能である。

配布時は、以下のようになっており、この場合は ``VADZC`` が用いられる。

.. code-block:: swift4
    :linenos:
    :lineno-start: 84

    let use_vad_pyannote: Bool = false
    var model_path: String? = Bundle.main.path(forResource: "pyannote_jit_script", ofType: "pt")

これに対し、以下のように ``use_vad_pyannote`` を true にすることで、 ``VADPyAnnote`` が用いられる。
なお、使用するモデルデータはあらかじめ iOS デバイスにコピーしておく必要があり（詳細割愛）、そのパスを ``model_path`` に指定する。

.. code-block:: swift
    :linenos:
    :lineno-start: 84

    let use_vad_pyannote: Bool = true
    var model_path: String? = Bundle.main.path(forResource: "pyannote_jit_script", ofType: "pt")


``VADPyAnnote`` による区間検出は今後チューニングを行っていくため、現時点では基本的に ``VADZC`` を用いた開発が望ましい。

音源情報
^^^^^^^^^^

HARK音響ライブラリは、音源の情報を管理するクラスとして、 ``Source`` を定義する。
詳細については、 :ref:`Sourceによる音源情報の管理` を参照されたい。

制限事項
==========

現時点では、HARK音響ライブラリについて以下のような制限がある。

* 実装されているノードは限定的であり、サンプルアプリケーションから参照されているものに限られている。
* 検証されているノード構成は、サンプルアプリケーションで定義されているものに限られている。
* PublishData で送信できるデータは、short[] に限られている。
* SubscribeData で受信できるデータは、NSDictionary に限られている。
