Tech Racho エンジニアの「?」を「!」に。
  • ライフ

未経験大学生によるPython入門日誌-第2話

アバンタイトル

Rubyの会社でPythonマン、片山です。

秋晴れが心地良い季節になりました。
毎日毎日温泉と甲羅酒のことしか考えていないので、あやうく相模大野で箱根方面に行ってしまいそうになりましたが、なんとか踏ん張って新宿までやってきました。

BPSには早く藤沢に引っ越してもらいたいです。

オープニング

情報系学部に在籍しながらにしてIT音痴の僕ですが、前回Python入門にチャレンジしたところ、「まぁPythonだったらやってあげてもいいかな?」という気持ちになったのでPython入門の記事を(は)続けることにしました。Ruby?いえ、知らない子ですね。

Aパート

OSCにチャレンジ!

OSCとはOpenSound Controlの略で、MIDI規格の後継として作られたものです。ナウな規格です。細かい説明は面倒なので真面目なネット百科事典におまかせしますが、要はアプリケーション間、デバイス間でのリアルタイム通信を行ってくれるものです。
応用すればちょっとイカしたことも出来そうなので、今回はPythonでOSCを扱ってみようと思います。

python-oscのインストール

OSC用のパッケージを探していたところ、python-oscというものが見つかって、無難そうなのでひとまずはこいつでやっていくことにしました。

$ pip3 install python-osc

僕の場合はpython3を使うのでpip3でインストールしました。

心配性なので先にimportできるかチェックしてみます。

$ python3
>>> import pythonosc
>>>

怒られなかったので無事インストールされているようです。

公式のサンプルを試してみます。

simple_client.py

import argparse
import random
import time

from pythonosc import osc_message_builder
from pythonosc import udp_client


if __name__ == "__main__":
  parser = argparse.ArgumentParser()
  parser.add_argument("--ip", default="127.0.0.1",
      help="The ip of the OSC server")
  parser.add_argument("--port", type=int, default=8000,
      help="The port the OSC server is listening on")
  args = parser.parse_args()

  client = udp_client.UDPClient(args.ip, args.port)

  for x in range(10):
    msg = osc_message_builder.OscMessageBuilder(address = "/filter")
    msg.add_arg(random.random())
    msg = msg.build()
    client.send(msg)
    time.sleep(1)

simple_server.py

import argparse
import math

from pythonosc import dispatcher
from pythonosc import osc_server

def print_volume_handler(unused_addr, args, volume):
  print("[{0}] ~ {1}".format(args[0], volume))

def print_compute_handler(unused_addr, args, volume):
  try:
    print("[{0}] ~ {1}".format(args[0], args[1](volume)))
  except ValueError: pass

if __name__ == "__main__":
  parser = argparse.ArgumentParser()
  parser.add_argument("--ip",
      default="127.0.0.1", help="The ip to listen on")
  parser.add_argument("--port",
      type=int, default=5005, help="The port to listen on")
  args = parser.parse_args()

  dispatcher = dispatcher.Dispatcher()
  dispatcher.map("/debug", print)
  dispatcher.map("/volume", print_volume_handler, "Volume")
  dispatcher.map("/logvolume", print_compute_handler, "Log volume", math.log)

  server = osc_server.ThreadingOSCUDPServer(
      (args.ip, args.port), dispatcher)
  print("Serving on {}".format(server.server_address))
  server.serve_forever()

サーバー側が受信する方、クライアント側がOSCを送信する方のプログラムになります。ちなみに、先にサーバー側(受信側)を起動しないとうまくいきませんが、これはどのアプリケーションでOSCを扱うときにも基本となります。

$ python3 simple_server.py
Serving on ('127.0.0.1', 5005)

127.0.0.1=localhostの5005番ポートで受信する用意が出来ました。
次はクライアント側(送信側)を起動します。

$ python3 simple_client.py

ん?動かない……。サンプルのくせに動かないとは何事だ!と若干苛々しながら原因を探していきます。
しばしリターンキーを叩く音がだんだんと大きくなって行く中、伏兵は意外なところにいました。

サンプルコードだからといって信用し切っていたため、なかなか気づかなかったのですが、上のソースコードでは単純にポート番号が一致していませんね。5005番に合わせて再チャレンジ。してみても受信側に何故か表示されず、Puredataを使ってみてやっとOSCが飛んでいるのを確認しました。

どうやら受信側のどこかに問題があるみたいですがどこをどうしたものか………と思って調べていた所、公式サイトのサンプル公式githubのサンプルが若干違うことに気づきました。githubのものではちゃんとポートが揃っている、送信側でもメッセージをprintしている等しっかりしていそうだったのでこちらを利用してみたらうまくいきました。

$ python3 simple_server.py
Serving on ('127.0.0.1', 5005)
$ python3 simple_client.py
Sending b'/filter\x00,f\x00\x00?\x03\x10\xf9'
Sending b'/filter\x00,f\x00\x00>J\xc3*'
Sending b'/filter\x00,f\x00\x00?0\x8a\x99'
Sending b'/filter\x00,f\x00\x00?k4\x8e'
Sending b'/filter\x00,f\x00\x00>\x17\xb30'
Sending b'/filter\x00,f\x00\x00?mP\x0b'
Sending b'/filter\x00,f\x00\x00>\x94kU'
Sending b'/filter\x00,f\x00\x00?2\xab\xbd'
Sending b'/filter\x00,f\x00\x00?\x15\xbdS'
Sending b'/filter\x00,f\x00\x00>\xbd\xb3U'

受信側では

Serving on ('127.0.0.1', 5005)
/filter 0.5119777321815491
/filter 0.19801011681556702
/filter 0.6896148324012756
/filter 0.9187706708908081
/filter 0.14814448356628418
/filter 0.9270026087760925
/filter 0.2898813784122467
/filter 0.69793301820755
/filter 0.5849201083183289
/filter 0.3705088198184967

みたいな感じになり成功しました。あー疲れた。。。

CM

最近ブラックビスケッツにハマっているんですが、iTunesにはブラビの曲がないのです。「タイミング」のビビアンカバーとかはあるんですけど……。
ほかにも90年代といえばRebecca、Lindbergあたりの邦楽(一部楽曲)は妙にハマりますね。

ちなみに筆者は毎年この時期になると、夜中に部屋で一人になって、稲垣潤一の「クリスマスキャロルの頃には」を聴いては寂しい気持ちになるのが恒例行事です。

Bパート

閑話休題

さてさて、先程は単にサンプルを動かしていただけであまり面白くなかったのでここからは少しひねりを加えていこうと思います。

ちょっとだけ応用してみる

ひねりもなにも、僕自身がひねくれ者ですので、「これ、わざわざPythonでやんの?アホなの?」ってことをやっていこうと思います。まぁまだそんなに難しいことは出来ないので、奇抜さでどうにかしようという作戦です。

ここでとりぃだしましたるはSuperColliderという音響開発環境。インストールは公式サイトからダウンロードしてパパッとやるだけなので省略します。ちなみに今回使ったSuperColliderのバージョンは3.7.2です。SuperColliderは立派な音響プログラミング環境ですが、ここではその説明はしません。というより出来ませんので今度勉強会に行ってきます。

こんなものを使って何をしようかといいますと、SuperColliderにOSCメッセージを送って音を出してみようということです。今回は簡単のためにSuperColliderに内蔵されているSuperDirtというサンプラー機能を使っていきます。こいつはポート57120番でOSCメッセージを受け付けてくれますのでそこにメッセージを送りつけて音を鳴らすということになります。

SuperCollider側の準備

こいつはめちゃくちゃ簡単です。まずSuperCollider IDEを起動してから

include("SuperDirt");

と入力して、その行をEvaluate(Command + Returnキー)してあげるとSuperDirtがインストールされます。既にインストールされていればこの作業は不要です。

インストール後、一度SuperCollider IDEを再起動する方がよいかもしれません。

SuperDirtを使いたければ、

SuperDirt.start;

と入力して、同じくその行をEvaluate(Command + Returnキー)するだけです。すると右下のPost Windowに

...
...
SuperDirt: listening to Tidal on port 57120

と出てきてSuperDirtが起動します。これで57120番のポートからSuperDirtと通信する準備が出来ました。
[listening to tidal on port 57120]と出てくるのは、本来この機能はTidalCyclesという環境との連携を考えられているからです。これについても今回は触れないでおきます。

次はPython側の準備です。こちらからはOSCメッセージを送信するので、先程参考にしたsimple_client.pyを改造していきます。
送りたいメッセージは/play2 cps s bdというものです。これさえ送れれば一応音が出るはずです。これについての細かい説明は面倒なので端折ります。

import argparse
import random
import time

from pythonosc import osc_message_builder
from pythonosc import udp_client


if __name__ == "__main__":
  parser = argparse.ArgumentParser()
  parser.add_argument("--ip", default="127.0.0.1",
      help="The ip of the OSC server")
  parser.add_argument("--port", type=int, default=5005,
      help="The port the OSC server is listening on")
  args = parser.parse_args()

  client = udp_client.UDPClient(args.ip, args.port)

  for x in range(10):
    msg = osc_message_builder.OscMessageBuilder(address="/filter")
    msg.add_arg(random.random())
    msg = msg.build()
    print("Sending", msg.dgram)
    client.send(msg)
    time.sleep(1)

上のクライアント側コードでまず直さなければならないのはポート番号でしょうか。default=5005から57120に変更します。ローカルでの通信なので127.0.0.1はそのままで。

お次はメッセージの中身を変えていきます。
/以下の内容をplay2にしたいのですが、これはaddress="filter"の部分を変更するとなんとかなりますね。
その後のアーギュメントを変更したいのですが、これは少々の試行錯誤の結果、msg.add_arg()を増やしてあげることでどんどん追加していくことができるようです。
それを踏まえて

import argparse
import random
import time

from pythonosc import osc_message_builder
from pythonosc import udp_client


if __name__ == "__main__":
  parser = argparse.ArgumentParser()
  parser.add_argument("--ip", default="127.0.0.1",
      help="The ip of the OSC server")
  parser.add_argument("--port", type=int, default=57120,
      help="The port the OSC server is listening on")
  args = parser.parse_args()

  client = udp_client.UDPClient(args.ip, args.port)

  for x in range(10):
    msg = osc_message_builder.OscMessageBuilder(address="/play2")
    msg.add_arg("cps")
    msg.add_arg("1")
    msg.add_arg("s")
    msg.add_arg("bd")
    msg = msg.build()
    print("Sending", msg.dgram)
    client.send(msg)
    time.sleep(1)

こうして、simple_client.pyを実行してあげることで見事に音を鳴らすことができました。大した応用ではなかったですかね。

エンディング

正直これをやって何になるんだという感じではありますが、何か応用できるようにしたいなと思います。

予告

何も考えてませんので次の記事を書くときの気分に任せたいと思います。

関連記事


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。