clojureで棒読みちゃんクライアント

ニコ生等でお馴染の「棒読みちゃん」のクライアントプログラムを作ってみました。socket通信なのでネット越しに棒読みさせることもできます。
棒読みちゃんWindowsのアプリですが、これを使えば仮想OSのLinuxからホストのWindowsで稼動している棒読みちゃんにコマンドを送る、みたいなことができます。もちろんネットで繋った別マシンからでも可能です。
ちなみに棒読みちゃんのポート番号はデフォルトで50001です。


追記 2011/5/17
clojure-1.2.0 では 128以上の整数を byte型へ変換しようとすると例外が発生するようになりました。Java の Byte型 の有効範囲 -128 ~ +127 に収まるよう、表現形式を変換する必要があります。

(ns bouyomi-client
  (:import [java.net Socket]))

;; clojure 1.1.0 ではこちらも通った。
;;(defn byte-nth-from-low [n offset]
;;  (bit-and (bit-shift-right n (* 8 offset)) 0xff))

;; clojure 1.2.0 ではこうしないと例外が発生する。
(defn byte-nth-from-low [n offset]
  (let [val (bit-and (bit-shift-right n (* 8 offset)) 0xff)]
    (if (> val 0x7f) ; 127
      (- val 0x100)
      val)))

(defn integer->byte-seq [#^Integer n]
  (->> (range 4)
       (map (partial byte-nth-from-low n))))

(defn short->byte-seq [#^Short n]
  (->> (range 2)
       (map (partial byte-nth-from-low n))))

(defn build-send-data [#^String msg]
  (let [str-bytes (.getBytes msg "UTF-8")
        header (->> (concat (short->byte-seq 1)   ; コマンド
                            (short->byte-seq -1)  ; 速度 (default)
                            (short->byte-seq -1)  ; 音程 (default)
                            (short->byte-seq -1)  ; 音量 (default)
                            (short->byte-seq 0)   ; 声質 (default)
                            [0]                   ; 文字コード (UTF-8)
                            (integer->byte-seq (count str-bytes))) ; 文字列のバイト長
                    (map byte)
                    (into-array Byte/TYPE))]
    [header str-bytes]))

(defn main [#^String host #^Integer port #^String msg]
  (let [[header msg] (build-send-data msg)
        sock (Socket. host port)]
    (doto (.getOutputStream sock)
      (.write header) (.write msg) (.close)))
  nil)

文字列だけでデータを送るプロトコルならもっと楽だったのですが、バイナリデータを送るにはどうするのか試行錯誤しました。
ShortやIntegerをバイト列に変換するメソッドがみつからなかったので自作しています。文字列をバイト列に変換するメソッド(getbytes)はあるんですけどね。