javaOSCを使ってVRCにOSC送受信してみました
はじめに
VRCにOSCが来てからもう結構経ちますね
この記事で紹介されている経路マッピングやOSCで画像送信など皆さん色々なことをOSCで実装されてて、制作意欲が湧いてきました
ということで私もOSCを使った何かしら実装したい!!!!
公式ページではC#のall-in-one branch of OscCoreやPythonのpython-oscが紹介されていますが、Kotlinで書きたかったので今回はJavaでOSCを扱えるライブラリのjavaOSCを試してみます
javaOSC
javaOSCとは、その名の通りjavaでOSCを扱うライブラリです
パースとか送受信を簡単に扱えるようにパッケージ化されています
GitHubで公開されていて、執筆時現在も更新が行われているようです
https://github.com/hoijui/JavaOSC
…ただ最新バージョンのjavaOSCについてはドキュメントが実質存在していないため、扱うのは面倒です
送信に関してはデモコードが公開されているのである程度はなんとかなりますが、受信については何も書かれていないのでソースを読む必要があります
そこで本記事ではjavaOSCを利用した基本的な送受信方法について扱います
検証環境
本記事での検証環境は以下のとおりです
- javaOSC 0.8
- Kotlin 1.7.0
- Java 8 (1.8.0_271)
- Gradle
 InteliJを用いて進めていきます
プロジェクト準備
InteliJにてKotlinプロジェクトを作成します
次にbuild.gradle.ktsのdependenciesに以下を追記します
javaOSC以外にLogBackを入れてますが、これは任意のロガーライブラリに変更可です
    implementation("com.illposed.osc:javaosc-core:0.8"){
        exclude("org.slf4j", "slf4j-log4j12")
        exclude("log4j", "log4j")
    }
    implementation("ch.qos.logback:logback-core:1.2.11")
    implementation("ch.qos.logback:logback-classic:1.2.11")
VRCへ送信
実装例
送信を行うソースは以下の通りです
本例ではEXParameterを変更して服を着せ替えています
import com.illposed.osc.OSCMessage
import com.illposed.osc.transport.OSCPortOut
import java.net.InetAddress
import java.net.InetSocketAddress
fun main(args: Array<String>) {
    val sendPort = 9000
    val ip = InetAddress.getLoopbackAddress()
    val sender = OSCPortOut(InetSocketAddress(ip, sendPort))
    val address = "/avatar/parameters/RoomWear"
    val params = mutableListOf(true)
    val message = OSCMessage(address, params)
    sender.send(message)
}
解説
実装例の解説です 主なところだけ
OSCPortOut(InetSocketAddress(ip, sendPort))
で送信用ソケットの生成とバインドをします
引数には送信先IPとPortを持ったInetSocketAddressを渡します
送信先IPはVRCを実行しているPCのアドレス、PortはVRC側の受付Portが9000なので、それを指定します
なお、OSCPortOutを生成するためのOSCPortOutBuilderがありますが、何故かうまく送信できなかったので直接生成しています
情報求む
次に、OSCMessage(address, params)で送信するメッセージを生成しています
引数にはパラメータのアドレスと値を渡します
アドレスについてはVRC内でOSCを有効化した後、
%HOMEPATH%\AppData\LocalLow\VRChat\VRChat\OSC\VRCユーザーID\Avatars
に生成されたファイルで確認できます
最後にsender.send(message)でメッセージの送信を行っています
VRCから受信
実装例
受信を行うソースは以下の通りです
本例ではAngularYの値を取得しています
import com.illposed.osc.MessageSelector
import com.illposed.osc.OSCMessageEvent
import com.illposed.osc.OSCMessageListener
import com.illposed.osc.transport.OSCPortInBuilder
import org.slf4j.LoggerFactory
import java.net.InetAddress
import java.net.InetSocketAddress
fun main(args: Array<String>) {
    val logger = LoggerFactory.getLogger("OSCListener")
    val listenerPort = 9001
    val ip = InetAddress.getLoopbackAddress()
    val builder = OSCPortInBuilder()
    builder.setSocketAddress(InetSocketAddress(ip, listenerPort))
    val listener = builder.build()
    val angularYSelector = object : MessageSelector{
        override fun isInfoRequired(): Boolean {
            return false
        }
        override fun matches(messageEvent: OSCMessageEvent?): Boolean {
            val msgAddress = messageEvent?.message?.address ?: return false
            val matchAddress = "/avatar/parameters/AngularY"
            return msgAddress.equals(matchAddress)
        }
    }
    val angularYListener = object : OSCMessageListener{
        override fun acceptMessage(event: OSCMessageEvent?) {
            val message = event?.message ?: return
            logger.info(message.address)
            message.arguments.forEach{logger.info(it.toString())}
        }
    }
    
    listener.dispatcher.addListener(angularYSelector, angularYListener)
    listener.startListening()
    Thread.sleep(10000)
}
解説
val builder = OSCPortInBuilder()
でBuilerを生成し、
builder.setSocketAddress(InetSocketAddress(ip, listenerPort))
で送信元IPとPortの指定、
builder.build()
でBuildして受信用ソケットの生成とバインドをします
受信では送信のOSCPortOutBuilderと違いOSCPortInBuilderが使えるので利用しますが、正直あまり違いがなさそうなのでOSCPortInで直接生成しても良さそうな気もします
送信元IPは先程と同じくVRCを実行しているPCのアドレスです
PortはVRC側の送信Portが9001なので、それを指定します
次に、MessageSelectorを実装したオブジェクトを生成します
MessageSelectorインターフェースはイベントのフィルタを行うインターフェースで、isInfoRequired()とmatches()メソッドを持っています
isInfoRequired()はmatches()でメタデータ(イベント発生時刻等)を利用するかどうかを指定します
matches()は受け取ったmessageEventが取得対象イベントであるかの確認を実装します
更に、OSCMessageListenerを実装したオブジェクトを生成します
OSCMessageListenerインターフェースは、受け取ったイベントに対しての処理を実装するインターフェースで、acceptMessage()メソッドを持ちます
acceptMessage()メソッドは、イベントを受け取ったときの処理を実装します
そして、listener.dispatcher.addListener(angularYSelector, angularYListener)
で生成したMessageSelectorとOSCMessageListenerをOSCPortInに登録します
最後に、listener.startListening()
で受付を開始します
なお、startListening()ではブロッキングが行われないため、別途ブロッキングが必要です
さいごに
これらはライブラリ内のソースコードで使いそうなところをさらっと見ただけなので想定された使い方と違う部分があるかもしれないです
なにかもっと良い使い方があればコメントしていただけると助かります
KotlinでOSCを扱う方法が分かったので、寝落ちしたときにミュートと表情の変更ができるシステムを作成中です
これで何も考えずに安全な寝落ちが可能になります!!!
夢が広がりますね!!!!!
追記
ソースコードがとてもおいしそうなスパゲッティになってて見せられないですけどできました
たのしいっすねOSC