メインコンテンツまでスキップ

チュートリアルアプリにOSCプロトコルを実装する

📚 Source Page

Open Sound Controlプロトコルを利用して、複数のアプリケーションをネットワークで接続する方法をご紹介します。アプリケーション間のインタラクションデータの送受信

レベル:中級

プラットフォーム:Windows, macOS, Linux, iOS, Android

クラス: OSCSender,OSCReceiver,OSCReceiver::Listener,OSCReceiver::ListenerWithOSCAddress,OSCMessage,OSCBundle

スタート

このチュートリアルにはいくつかのデモ・プロジェクトがあります。これらのプロジェクトのダウンロード・リンクは、チュートリアルの関連セクションに記載されています。

各セクションのこのステップでヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.

デモ・プロジェクト

このチュートリアルで提供されるデモ・プロジェクトは、OSCとのインタラクションに必要な様々なアプリケーションを紹介します。要約すると、これらのアプリケーションは以下の通りです:

  • OSCセンダー:センダーアプリケーションにはロータリーノブがあり、他のインスタンスに情報を送信する。
  • OSCレシーバー:レシーバー・アプリケーションは、センダー・インスタンスに接続し、情報を受信して処理・表示する。
  • OSCモニター:モニター・アプリケーションも送信者インスタンスに接続するが、より正確にやりとりをモニターし、ログに記録する。
注記

このチュートリアルが正しく機能するためには、送信側と受信側/モニター側のアプリケーションを同時に1つだけ開く必要があります。

ここで紹介するコードは、JUCE ExamplesのOSCデモアプリとほぼ同様です。

OSCトランスミッター

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

完成すると、OSCセンダー・アプリケーションは1つのロータリー・ノブを表示し、起動時にそれを操作できるようになる:

OSC送信アプリのウィンドウ
OSC送信アプリのウィンドウ

OSCトランスミッターの実装

まずは送信者アプリケーションの実装から始めよう。

の中でMainContentComponentクラスでは、まずこのアプリケーションのプライベート・メンバー変数を次のように宣言する:

    juce::Slider rotaryKnob;    // [1]
juce::OSCSender sender; // [2]

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

追加Sliderオブジェクトを使用してユーザーのインタラクションをキャプチャします。[1]そしてOSCSenderオブジェクトを後でレシーバーに接続する[2].

クラスのコンストラクタで、スライダー・パラメータを設定し、以下のようにネットワークへの接続を試みる:

    MainContentComponent()
{
rotaryKnob.setRange (0.0, 1.0);
rotaryKnob.setSliderStyle (juce::Slider::RotaryVerticalDrag);
rotaryKnob.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 150, 25);
rotaryKnob.setBounds (10, 10, 180, 180);
addAndMakeVisible (rotaryKnob); // [3]
        // specify here where to send OSC messages to: host URL and UDP port number
if (! sender.connect ("127.0.0.1", 9001)) // [4]
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");

setSize (200, 200);
}
  • [3]スライダーの範囲、スタイル、境界を設定しComponentをこのクラスに加える。
  • [4]ここではローカルホストまたはIPアドレス "127.0.0.1 "をUDPポート番号9001で呼び出す。connect()関数を使用する。OSCSenderオブジェクトを呼び出します。接続に失敗した場合は、プライベート関数showConnectionErrorMessage()エラーメッセージをユーザーに表示するために、後で宣言する。
注記

このチュートリアルでは、後でレシーバーで指定する任意のポート番号で、開発マシンのローカル・ネットワークに接続する。

次にSlider::onValueChangeコールバックSliderオブジェクトを作成し、OSCメッセージをレシーバー・アプリケーションに送信する:

        rotaryKnob.onValueChange = [this]
{
// create and send an OSC message with an address and a float value:
if (! sender.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
showConnectionErrorMessage ("Error: could not send OSC message.");
};

ラムダ関数の中でsend()関数を使用する。OSCSenderオブジェクトのアドレスとロータリーノブの値[5].ここで"/juce/rotaryknob "として提供されるアドレスは、後で受信機でメッセージを分類することを可能にする。メッセージの送信に失敗した場合は、同じヘルパー関数showConnectionErrorMessage()をクリックするとエラーが表示されます。

この機能を実装するには、関数[AlertWindow::showMessageBoxAsync()](https://docs.juce.com/master/classAlertWindow.html#abf9c608d6e03ddf828fa9629cff1418b "Shows a dialog box that just has a message and a single button to get rid of it."):

    void showConnectionErrorMessage (const juce::String& messageText)
{
juce::AlertWindow::showMessageBoxAsync (juce::AlertWindow::WarningIcon,
"Connection error",
messageText,
"OK");
}

これで送信側の実装は完了した。

注記

この修正版のソースコードはOSCSenderTutorial_02.hファイルにある。

OSCレシーバー

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

完成すると、OSCレシーバー・アプリケーションは、起動時に操作できない1つのロータリー・ノブを表示する:

OSCレシーバーアプリのウィンドウ
OSCレシーバーアプリのウィンドウ

OSCレシーバーの実装

レシーバーを実装するにはSliderオブジェクトMainContentComponentクラスで、送り手のロータリー・ノブで行った変更を反映させる:

    //==============================================================================
juce::Slider rotaryKnob;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

を宣言する代わりにOSCSenderオブジェクトをメンバーとして継承する。OSCReceiverのサブクラスとしてMainContentComponentを実装する。[1]:

class MainContentComponent   : public juce::Component,
private juce::OSCReceiver, // [1]
private juce::OSCReceiver::ListenerWithOSCAddress // [2]
{
public:

また、メッセージ受信時にコールバックを受け取るために、OSCReceiver::ListenerWithOSCAddress<OSCReceiver::MessageLoopCallback>を継承する必要があります。[2].

クラスのコンストラクタで、同じパラメータをSliderオブジェクトを作成する:

    MainContentComponent()
{
rotaryKnob.setRange (0.0, 1.0);
rotaryKnob.setSliderStyle (juce::Slider::RotaryVerticalDrag);
rotaryKnob.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 150, 25);
rotaryKnob.setBounds (10, 10, 180, 180);
rotaryKnob.setInterceptsMouseClicks (false, false);
addAndMakeVisible (rotaryKnob);

// specify here on which UDP port number to receive incoming OSC messages
if (! connect (9001)) // [3]
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");

// tell the component to listen for OSC messages matching this address:
addListener (this, "/juce/rotaryknob"); // [4]

setSize (200, 200);
}

のサブクラスとしてOSCReceiverメッセージを受信するには、正しいポート番号に直接接続してください。[3]をリッスンするアドレスを提供し、このクラスを自分自身のリスナーとして登録する。[4].

警告

ポート番号とOSCアドレスが送信側アプリケーションと一致していることを確認してください。

(1)の場合OSCMessageオブジェクトを受信するとoscMessageReceived()コールバック関数が呼び出される:

    void oscMessageReceived (const juce::OSCMessage& message) override
{
if (message.size() == 1 && message[0].isFloat32()) // [5]
rotaryKnob.setValue (juce::jlimit (0.0f, 10.0f, message[0].getFloat32())); // [6]
}

この関数をオーバーライドして、まずメッセージのサイズと値の型をチェックする。[5].次に、ロータリー・ノブの値を設定します。Slider範囲[6].

接続に失敗した場合に備えて、送信側アプリケーションと同じエラー・ロギング機能を実装する:

    void showConnectionErrorMessage (const juce::String& messageText)
{
juce::AlertWindow::showMessageBoxAsync (juce::AlertWindow::WarningIcon,
"Connection error",
messageText,
"OK");
}

これで、両方のアプリケーションを起動したときに、送信側ノブを動かして受信側ノブをコントロールできるはずです。

注記

この修正版のソースコードはOSCReceiverTutorial_02.hファイルにある。

OSCモニター

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

完了すると、OSCモニターアプリケーションはやりとりを記録するウィンドウを表示します。送信者インスタンスなしで最初に起動した場合、モニター・アプリケーションは接続できず、ウィンドウは次のように表示されます:

OSCモニターアプリのウィンドウ
OSCモニターアプリのウィンドウ

センダー・インスタンスの実行中に起動すると、モニター・アプリケーションはそのインスタンスに接続できるようになり、ウィンドウは次のように表示されます:

OSCモニターアプリのウィンドウ
OSCモニターアプリのウィンドウ

OSCモニター実施

を表示する別のタイプのレシーバーを実装してみよう。OSCMessageオブジェクトをテキストとして扱う。

の中でMainContentComponentクラスを継承していることがわかります。OSCReceiver::Listenerクラス[1].を継承していないことに注意してほしい。OSCReceiver::ListenerWithOSCAddressアドレスに関係なく送信されたすべてのメッセージを受信したいからだ:

class MainContentComponent   : public juce::Component,
private juce::OSCReceiver::Listener // [1]
{
public:

OSCLogListBox とOSCReceiverオブジェクトをプライベート・メンバ変数として使用します。[2]を定義し、ポート番号を格納する一時的なint変数を定義する。[3]以下の通りである:

    OSCLogListBox oscLogListBox;        // [2]
juce::OSCReceiver oscReceiver;

int currentPortNumber = -1; // [3]

コンストラクターで、対応するパラメーターを以下のように設定する:

    MainContentComponent()
{
        oscLogListBox.setBounds (0, 60, 700, 340);  // [4]
addAndMakeVisible (oscLogListBox);

oscReceiver.addListener (this); // [5]
oscReceiver.registerFormatErrorHandler ([this] (const char* data, int dataSize) // [6]
{
oscLogListBox.addInvalidOSCPacket (data, dataSize);
});

setSize (700, 400);
}
  • [4]OSCLogListBoxに境界を設定してComponentが見える。
  • [5]このクラスをOSCReceiverオブジェクトでコールバックを受け取ります。
  • [6]: カスタムエラーハンドラフォーマットをOSCReceiverをラムダ関数で返します。ここでは、解析に失敗したデータへのポインタとそのサイズをOSCLogListBoxオブジェクトに渡している。

モニター・アプリケーションのユーザー・インターフェース上でボタンがクリックされると、次のステップで実装する対応する関数を呼び出します。

ユーザーが "connect "ボタンをクリックした場合、アプリケーションの状態に応じて接続/切断を行う。[7].さらに、「接続」ボタンが押されるたびにupdateConnectionStatusLabel()ヘルパー関数[8]この通りである:

    void connectButtonClicked()
{
if (! isConnected()) // [7]
connect();
else
disconnect();

updateConnectionStatusLabel(); // [8]
}

ステータス・ラベルを更新するには、以下のように接続状態に応じてテキストと色を変更するだけです:

    void updateConnectionStatusLabel()
{
juce::String text = "Status: ";

if (isConnected())
text += "Connected to UDP port " + juce::String (currentPortNumber);
else
text += "Disconnected";

auto textColour = isConnected() ? juce::Colours::green : juce::Colours::red;

接続状態を判断するために、ポート番号がデフォルト値の-1から変更されたかどうかをチェックする:

    bool isConnected() const
{
return currentPortNumber != -1;
}

そうでなければ、ユーザーが "clear "ボタンを押したとき、単純にOSCLogListBoxにclear命令を送り、スクリーン・ログをリセットする:

    void clearButtonClicked()
{
oscLogListBox.clear();
}

送信側アプリケーションに接続するためにconnect()関数である:

    void connect()
{
auto portToConnect = portNumberField.getText().getIntValue(); // [9]

if (! isValidOscPort (portToConnect)) // [10]
{
handleInvalidPortNumberEntered();
return;
}

if (oscReceiver.connect (portToConnect)) // [11]
{
currentPortNumber = portToConnect;
connectButton.setButtonText ("Disconnect");
}
else // [12]
{
handleConnectError (portToConnect);
}
}
  • [9]ポート番号のテキストをint値に変換し、一時変数に格納する。
  • [10]ポート番号が無効な場合は、次のようなエラーメッセージを表示します。handleInvalidPortNumberEntered()ヘルパー関数。
  • [11]そうでなければ、ポート番号が有効であることを意味し、接続を試みることができる。接続に成功したら、一時的なポート番号変数を正しい値に更新し、「接続」ボタンのテキストを変更する。
  • [12]接続に失敗した場合は、対応するhandleConnectError()ヘルパー関数でエラーを表示する。

ポート番号が有効であることを確認するために、以下の範囲に該当するかどうかをチェックしてください。1 ..65535包括的だ:

    bool isValidOscPort (int port) const
{
return port > 0 && port < 65536;
}

ネットワークから切断するには、切断が成功したかどうかを確認し、成功した場合はポート番号を-1にリセットし、「接続」ボタンのテキストを変更する。[13].それ以外の場合はhandleDisconnectError()ヘルパー関数[14]:

    void disconnect()
{
if (oscReceiver.disconnect()) // [13]
{
currentPortNumber = -1;
connectButton.setButtonText ("Connect");
}
else
{
handleDisconnectError(); // [14]
}
}

(1)の場合OSCMessageを受信すると、以下のコールバック関数が呼び出され、メッセージの内容がOSCLogListBoxに転送される:

    void oscMessageReceived (const juce::OSCMessage& message) override
{
oscLogListBox.addOSCMessage (message);
}

もしOSCBundleを受信すると、別のコールバック関数が呼び出され、バンドルの内容を OSCLogListBox に転送する:

    void oscBundleReceived (const juce::OSCBundle& bundle) override
{
oscLogListBox.addOSCBundle (bundle);
}

エラーの種類ごとにメッセージボックスを表示し、実装を完了する:

    //==============================================================================
void handleConnectError (int failedPort)
{
juce::AlertWindow::showMessageBoxAsync (juce::AlertWindow::WarningIcon,
"OSC Connection error",
"Error: could not connect to port " + juce::String (failedPort),
"OK");
}

//==============================================================================
void handleDisconnectError()
{
juce::AlertWindow::showMessageBoxAsync (juce::AlertWindow::WarningIcon,
"Unknown error",
"An unknown error occured while trying to disconnect from UDP port.",
"OK");
}

//==============================================================================
void handleInvalidPortNumberEntered()
{
juce::AlertWindow::showMessageBoxAsync (juce::AlertWindow::WarningIcon,
"Invalid port number",
"Error: you have entered an invalid UDP port number.",
"OK");
}

これで、送信側のロータリー・ノブが操作されると、モニター・アプリケーションは受信したすべてのメッセージをログに記録し、ウィンドウは次のようになります:

OSCモニターアプリのウィンドウ
OSCモニターアプリのウィンドウ
注記

この修正版のソースコードはOSCMonitorTutorial_02.hファイルにある。

エクササイズ

以下のような他のGUIコンポーネントを使用して、異なるOSCメッセージを扱うように送信側と受信側のアプリケーションを修正する。ToggleButtonまたはComboBoxオブジェクトがある。

概要

このチュートリアルでは、アプリケーション・インスタンス間で情報を送信するためのOSCプロトコルの実装方法を学びました。特に

  • 送信側アプリケーションのロータリーノブを使ってOSCメッセージを送信。
  • レシーバー・アプリケーションに接続し、対応するメッセージを処理する。
  • モニター・アプリケーションを使用して、OSCとのやりとりを冗長な方法で表示。

参照