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

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

📚 Source Page

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

レベル: 中級
プラットフォーム: Windows, macOS, Linux, iOS, Android
クラス: OSCSender, OSCReceiver, OSCReceiver::Listener, OSCReceiver::ListenerWithOSCAddress, OSCMessage, OSCBundle

はじめに

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

各セクションでこの手順についてサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucerを始めようを参照してください。

デモプロジェクト

このチュートリアルで提供されるデモプロジェクトは、OSCインタラクションに必要なさまざまなアプリケーションを示しています。要約すると、これらのアプリケーションは:

  • OSC Sender:送信アプリケーションはロータリーノブを含み、他のインスタンスに情報を送信します。
  • OSC Receiver:受信アプリケーションは送信インスタンスに接続し、処理して表示するための情報を受信します。
  • OSC Monitor:モニターアプリケーションも送信インスタンスに接続しますが、インタラクションをより正確に監視およびログします。
ヒント

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

注記

ここで紹介するコードは、JUCE ExamplesのOSCデモアプリと大まかに類似しています。

OSC Sender

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

完成すると、OSC送信アプリケーションは起動時にインタラクションできる単一のロータリーノブを表示します:

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

OSC Senderの実装

送信アプリケーションの実装を始めましょう。

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]
// OSCメッセージの送信先を指定:ホストURLとUDPポート番号
if (!sender.connect ("127.0.0.1", 9001)) // [4]
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");

setSize (200, 200);
}
  • [3]:スライダーの範囲、スタイル、境界を設定し、このクラスにComponentを追加します。
  • [4]:ここでは、OSCSenderオブジェクトでconnect()関数を呼び出すことで、localhostまたはIPアドレス「127.0.0.1」のUDPポート番号9001に接続を試みます。接続が失敗した場合、後で宣言するプライベート関数showConnectionErrorMessage()を呼び出してユーザーにエラーメッセージを表示します。
ヒント

このチュートリアルの目的のために、受信機で後で指定する任意のポート番号で開発マシンのローカルネットワークに接続します。

次に、受信アプリケーションにOSCメッセージを送信するためにSliderオブジェクトのSlider::onValueChangeコールバックを以下のように実装します:

rotaryKnob.onValueChange = [this] {
// アドレスとfloat値を持つOSCメッセージを作成して送信:
if (!sender.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
showConnectionErrorMessage ("Error: could not send OSC message.");
};

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

この関数は、AlertWindow::showMessageBoxAsync()関数を使用してダイアログボックスにエラーテキストを非同期で表示することで実装します:

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

送信機の実装が完了しました。

ヒント

このコードの変更版のソースコードは、デモプロジェクトのOSCSenderTutorial_02.hファイルにあります。

OSC Receiver

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

完成すると、OSC受信アプリケーションは起動時にインタラクションできない単一のロータリーノブを表示します:

OSC受信アプリウィンドウ
OSC受信アプリウィンドウ

OSC Receiverの実装

受信機を実装するには、送信機のロータリーノブで行われた変更を反映するためにMainContentComponentクラスでSliderオブジェクトを宣言するだけです:

//==============================================================================
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<juce::OSCReceiver::MessageLoopCallback> // [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);

// OSCメッセージを受信するUDPポート番号を指定
if (!connect (9001)) // [3]
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");

// このアドレスに一致するOSCメッセージをリッスンするようコンポーネントに指示:
addListener (this, "/juce/rotaryknob"); // [4]

setSize (200, 200);
}

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

警告

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

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 Monitor

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

完成すると、OSCモニターアプリケーションはインタラクションをログするウィンドウを表示します。送信インスタンスなしで最初に起動すると、モニターアプリケーションは接続できず、ウィンドウは以下のようになります:

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

送信インスタンスが実行されている間に起動すると、モニターアプリケーションはそれに接続でき、ウィンドウは以下のようになります:

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

OSC Monitorの実装

OSCMessageオブジェクトをテキストとして表示する異なるタイプの受信機を実装しましょう。

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

class MainContentComponent : public juce::Component,
private juce::OSCReceiver::Listener<juce::OSCReceiver::MessageLoopCallback> // [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]。さらに、「connect」ボタンが押されるたびに、ここに示すように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にクリア命令を送信して画面ログをリセットするだけです:

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]:それ以外の場合、ポート番号が有効であることを意味し、接続を試みることができます。接続が成功した場合、一時ポート番号変数を正しい値に更新し、「connect」ボタンのテキストを変更します。
  • [12]:接続が失敗した場合、対応するhandleConnectError()ヘルパー関数を呼び出してエラーを表示します。

ポート番号が有効であることを確認するには、範囲が1 .. 65535の範囲内(両端を含む)かどうかを確認します:

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

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

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

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ファイルにあります。

注記

演習:ToggleButtonComboBoxオブジェクトなどの他のGUIコンポーネントを使用して、異なるOSCメッセージを処理するように送信機と受信機アプリケーションを変更してください。

まとめ

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

  • 送信アプリケーションでロータリーノブを使用してOSCメッセージを送信しました。
  • 受信アプリケーションに接続し、対応するメッセージを処理しました。
  • モニターアプリケーションを使用してOSCインタラクションを詳細に表示しました。

関連項目