The jonki

呼ばれて飛び出てじょじょじょじょーんき

【C#】 Open Sound ControlをC#で使う

 Kinectを買ったらサンプルがWPFアプリケーションばかりで使いにくいシロモノでした。Kinectに限らず得られたデータを他のアプリに渡したいというときは山ほどあるかと思いますが、個人的にはOpen Sound Control (略してosc)というデータ通信のライブラリが便利だと思います。oscについては田所先生のページが分かりやすいです。要は自分でソケットプログラム書かなくても、結構いろんなプラットフォーム間でデータやりとりできるよ、というものです。

ということでC#でググってみるといくつか見つかるのだけど、そのなかでもMake Controller Kit .NETというのが便利そう。ドキュメント内にサンプルがありますが、シンプルで良さそうです。


ということで実装。

Sender側

今回はudpを投げるようにします。サンプルではUSBも楽ちんそうです。

MakeControllerOsc.dllを作る。

1. ダウンロード
まずmakingthings.comのサイトに飛び、下記ページの「.NET C# v0.2」のリンクからdotnet.zipをダウンロードします。
http://www.makingthings.com/resources/downloads
2. 設定
展開したフォルダからdotnet.slnをVisual Studio express等で開き、ソリューションエクスプローラーで【MakeControllerOsc】というプロジェクトを右クリックし、【スタートアッププロジェクト】に設定します。また、DebugビルドになっているのでReleaseビルドに変えます。上のプルダウンのメニューから見つけてね。
4. ビルド
ソリューションのビルドでdotnet\MakeControllerOsc\bin\Release\MakeControllerOsc.dllといった形でdllができます。
あとはこれを自分のC#のプログラムで使いまわせます。

OscSenderアプリを作る(C# WPF)

Kinectを使ってる関係でC# WPFです。.NETならおk。
1. まず空の【WPFアプリケーション】を作成
2. 作成したMakeControllerOsc.dllをプロジェクトの.csprojファイルや他のソースファイルがあるところにコピー。
3. 【ソリューションエクスプローラー】の【参照設定】を右クリックで、【参照の追加】->【参照】でコピーしてきたMakeControllerOsc.dllを設定
これでusing MakingThingsとかすればOSCが使えるようになります。
4. 【ツールボックス】からButtonを選んで、ウインドウに配置、それをダブルクリックすると、クリックアクションのコードが書けるように。
今回はクリックタイミングにしたけどお好きな時にどうぞ。


MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

//!< MakeController
using MakingThings;

namespace OscSenderExample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private UdpPacket udpPacket;
        private Osc oscUdp;
        OscMessage oscMsg;

        public MainWindow()
        {
            //!< osc udp
            udpPacket = new UdpPacket();
            udpPacket.RemoteHostName = "127.0.0.1";
            udpPacket.RemotePort = 8000;
            udpPacket.LocalPort = 9000;
            udpPacket.Open();
            oscUdp = new Osc(udpPacket);
            InitializeComponent();
        }

        private void Send_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine("send data");
            //パスはアプリの仕様で適当に、今回はウインドウの幅を送ります。
            oscMsg = Osc.StringToOscMessage("/event/click " + Width.ToString());
            oscUdp.Send(oscMsg);
        }
    }
}
MainWindow.xaml
<Window x:Class="OscSenderExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="Send" Height="23" HorizontalAlignment="Left" Margin="224,136,0,0" Name="Send" VerticalAlignment="Top" Width="75" Click="Send_Click" />
    </Grid>
</Window>

Receiver側

osc対応してるなら何でも良いんだけど今回はopenFrameworksを使います。oFならaddonsExamplesの中にoscReceiveExampleとかあるので、それを参考にすれば瞬殺かと。

testApp.h

#include "ofMain.h"
#include "ofxOsc.h"

#define PORT 8000
#define NUM_MSG_STRINGS 60

class testApp : public ofBaseApp{
	public:
		//
		//中略
		//
		ofxOscReceiver		receiver;


testApp.cpp

//--------------------------------------------------------------
void testApp::setup(){
	// listen on the given port
	cout << "listening for osc messages on port " << PORT << "\n";
	receiver.setup( PORT );
}

//--------------------------------------------------------------
void testApp::update(){

	// check for waiting messages
	while( receiver.hasWaitingMessages() )
	{
		// get the next message
		ofxOscMessage m;
		receiver.getNextMessage( &m );
		
		if(m.getAddress() == "/event/click") {
			printf("val=%d\n", m.getArgAsInt32(0));
		}
	}
}
//
//略
//

番外編(スレッドでOSCを送る)

例えばリアルタイムにどんどんデータを送る際には下記のようにバックグラウンドで送るというのもありかと思います。

MainWindow.xaml.cs

        //!< thread
        private readonly BackgroundWorker sendWorker = new BackgroundWorker();

        public MainWindow()
        {
            //!< udp
            udpPacket = new UdpPacket();
            udpPacket.RemoteHostName = "127.0.0.1";
            udpPacket.RemotePort = 8000;
            udpPacket.LocalPort = 9000;
            udpPacket.Open();
            oscUdp = new Osc(udpPacket);

            //!< thread
            sendWorker.DoWork += send_DoWork;
        }

	//
	//中略
	//

	//例えばkinectのフレームアップデートの際に
        private void kinect_allFramesReady(object sender, AllFramesReadyEventArgs e)
        {
            using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
            {
                depthImg.Source = depthFrame.ToBitmapSource();
            }

            if (!sendWorker.IsBusy)
            {
                //Console.WriteLine("not busy");
                audioBeamAngle = audioManager.getAudioBeamAngle();
                audioSourceAngle = audioManager.getAudioSourceAngle();
                audioConfidence = audioManager.getAudioConfidence();
                sendWorker.RunWorkerAsync();
            }
        }
        private void send_DoWork(object sender, DoWorkEventArgs e)
        {
                //ここに上記のoscをsendするプログラムを書く
	}


oF上でデータがいろいろと扱えると、夢がいっきに広がりますね。