javaOSCでVRCのOSC送受信


javaOSCを使ってVRCにOSC送受信してみました

# はじめに
VRCにOSCが来てからもう結構経ちますね

https://qiita.com/SatoshiGachiFujimoto/items/7f43954c7cbbe8c25dbe

この記事で紹介されている[経路マッピング](https://qiita.com/SatoshiGachiFujimoto/items/7f43954c7cbbe8c25dbe#%E7%B5%8C%E8%B7%AF%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0)や[OSCで画像送信](https://qiita.com/SatoshiGachiFujimoto/items/7f43954c7cbbe8c25dbe#osc%E3%81%A7%E7%94%BB%E5%83%8F%E9%80%81%E4%BF%A1)など皆さん色々なことをOSCで実装されてて、制作意欲が湧いてきました
ということで私もOSCを使った何かしら実装したい！！！！

[公式ページ](https://docs.vrchat.com/docs/osc-overview)ではC#の[all-in-one branch of OscCore](https://github.com/vrchat/osccore/tree/all-in-one)やPythonの[python-osc](https://github.com/attwad/python-osc)が紹介されていますが、Kotlinで書きたかったので今回はJavaでOSCを扱えるライブラリの[javaOSC](https://github.com/hoijui/JavaOSC)を試してみます

# javaOSC
javaOSCとは、その名の通りjavaでOSCを扱うライブラリです
パースとか送受信を簡単に扱えるようにパッケージ化されています
GitHubで公開されていて、執筆時現在も更新が行われているようです
[https://github.com/hoijui/JavaOSC](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を入れてますが、これは任意のロガーライブラリに変更可です
```gradle:gradle:buile.gradle.kts dependencies
    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を変更して服を着せ替えています
```Kotlin:Sender.kt
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)
}
```

https://twitter.com/SCPScTi/status/1537356555665956864?s=20&t=6st4PdoO9KztZ--AnhaCgA

## 解説
実装例の解説です　主なところだけ

`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の値を取得しています

```Kotlin:Listener.kt
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)
}
```

https://twitter.com/SCPScTi/status/1537359828070240256?s=20&t=6st4PdoO9KztZ--AnhaCgA

## 解説
`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を扱う方法が分かったので、寝落ちしたときにミュートと表情の変更ができるシステムを作成中です
これで何も考えずに安全な寝落ちが可能になります！！！
夢が広がりますね！！！！！

https://twitter.com/SCPScTi/status/1537735607589666816?s=20&t=e9UTL9Mxm2uvX-myOeDw_A

追記
ソースコードがとてもおいしそうなスパゲッティになってて見せられないですけどできました
たのしいっすねOSC


# 参考
- [OSC Overview - VRChat Documentation](https://docs.vrchat.com/docs/osc-overview)
- [VRChatでOSCやーる（Python3.9、Windows10）](https://qiita.com/SatoshiGachiFujimoto/items/7f43954c7cbbe8c25dbe)
- [javaOSC](https://github.com/hoijui/JavaOSC)
- [ジャバでOSC(Open Sound Control)を扱うメモ](https://soma.hatenablog.jp/entry/2015/08/28/120215)