【TidalCycles】TidalCyclesを外部からOSC通信で動かす

# はじめに
TidalCyclesは、Haskellライクなコードによってリズムパターンを生成できるソフトウェアです。

[【TidalCycles】ライブコーディングによる作曲のすすめ](https://qiita.com/s2hap/items/620082b82a4bbe475823)という記事を書いているので、TidalCyclesをこれから使ってみたい方などはこちらも合わせてご確認ください。

# OSC通信とは

[ウィキペディアのOSC(OpenSound Control)](https://ja.wikipedia.org/wiki/OpenSound_Control)には

> OSC はMIDIの代替となることを意図して設計されている。MIDIは1982年に実装されたもので、最近のマルチメディア用途には適していない部分が多い。通信プロトコルであるため、OSCによって、楽器やMIDIコントローラや各種マルチメディア機器が屋内のネットワーク（TCP/IP、イーサネット）やインターネットを経由して通信することが可能となる。OSCはブロードバンド・ネットワークの通信速度を最大限に活かしてデータ転送を行うため、31.250[kbps]と言う規格上の速度上限があったMIDIでは不可能な新たな利用方法が可能となっている。また、転送データの柔軟性も増しており、より高度なレベルでの通信が可能である。
> OSCは様々なプロトコル上で転送可能だが、一般にUDPが使われる。

と説明されています。つまり音楽の通信目的で使用されるもので、例えばソフトウェアのパラメータなどを外部からいじれる仕組みです。

# SuperColliderを外部から再生する

早速OSCを介してSuperColliderのシンセを鳴らしてみたいですが、OSCの送信にはSuperColliderのエディタを用いたいと思います(OSCさえ送信できれば何のアプリをつかっても良いです)

SuperColliderのデフォルトポートは57110です。そのため、デフォルトで定義されているシンセ[superfm](https://github.com/musikinformatik/SuperDirt/blob/15f26e33a06f2a7a42cf4b1f9756e99d14a8a376/library/default-synths-extra.scd#L556-L639)を一発鳴らすOSCは次のようにかけます

```supercollider
(
var osc = NetAddr.new("localhost", 57110);

osc.sendMsg("/s_new", \superfm, 1000, 1, 0, \freq, 220, \sustain, 0.3);
)
```

また、SuperDirtのデフォルトポートは57120なので、TidalCyclesで普段鳴らす単発の音をOSC経由で再生することもできます

```supercollider
(
var osc = NetAddr.new("localhost", 57120);

// supercollider側のコンソールで「late 0.017549354」みたいなメッセージが出るのを防ぐため、osc messageに"latency" 0.1を追加したほうがいいです
osc.sendMsg("/dirt/play", "gain", 1, "n", 5, "pan", 0.5, "s", "hc", "room", 0.4, "latency", 0.1);
)
```

これはTidalCyclesで以下のコードを実行したときと同じ音が再生されるかと思います。そのため単発の音を出したいだけでしたらこの方法だけで問題ありません。

```haskell
once
  $ s "hc"
  # gain 1 # n 5 # pan 0.5 # room 0.4
```

# TidalCyclesで再生中の音をOSCでパラメータ変更

こちらが本題です。
単発の音を出すわけではなく、既に再生中のリズムパターンに対してエフェクト値やpan、gainなどを変更させていきたいです。

## BootTidal.hsへの追記

まずTidalCyclesへのOSC通信を許可するために、BootTidal.hsへ以下のような変更を加えます(参考: http://tidalcycles.org/docs/configuration/MIDIOSC/osc/#controller-input)

```diff:BootTidal.hs
- tidal <- startTidal (superdirtTarget {oLatency = 0.1, oAddress = "127.0.0.1", oPort = 57120}) (defaultConfig {cVerbose = True, cFrameTimespan = 1/20})
+ tidal <- startTidal (superdirtTarget {oLatency = 0.1, oAddress = "127.0.0.1", oPort = 57120}) (defaultConfig {cFrameTimespan = 1/20,cCtrlAddr = "0.0.0.0", cCtrlPort = 6060})
```

OSC通信されたものを判定するために、BootTidal.hsに以下のcapply関数追記します(参考: https://club.tidalcycles.org/t/trigger-tidal-code-with-midi/2168)

```haskell:BootTidal.hs
:{
capply
  :: (Ord a1, Num a1) =>
     Pattern a1
     -> (Pattern a2 -> Pattern a2) -> Pattern a2 -> Pattern a2
capply condpat effectpat = every
    (fmap (\x -> if x > 0 then 1 else 0)
    (segment 1 condpat)) (effectpat)
:}
```

追記、保存したらTidalCyclesを再起動させます。デフォルトのBootTidal.hsを変更させたくない場合は、TidalCyclesのプロジェクトファイル直下にBootTidal.hsを置くとそちらが参照してもらえます。

## 実装

これで準備できたので、TidalCyclesのコードを書いていきます。

まずは以下のコードをみてください

```haskell
d1
  $ capply (cF 0 "test") (const $ s "808*4")
  $ s "bd*4"
  # pan (cF 0.5 "pan")
```

これを実行するとbd(バスドラ)が1サイクルに4回ずつなると思います。気になるのは先ほど作成したcapply関数の部分と、panのあとの`(cF 0.5 "pan")`部分だと思います。

再生中に以下のOSCをSuperColliderから送信してみてください


```supercollider
(
var osc = NetAddr.new("localhost", 6060);

osc.sendMsg("/ctrl", "pan", 1);
)
```

すると、panの位置が右にずれたと思います。つまり、`(cF 0.5 "pan")`は**初期値が0.5で、"pan"に対してOSC送信された値が格納される**ということみたいです。

次に、以下のOSCをSuperColliderから送信してみてください


```supercollider
(
var osc = NetAddr.new("localhost", 6060);

osc.sendMsg("/ctrl", "test", 1);
)
```

すると今度は、bdではなく808が再生されるようになったと思います。`capply (cF 0 "test") (const $ s "808*4")`の意味は、**"test"に対して送信された値が0より大きければ808を再生、そうでなければbdを再生する**という意味になります。

ちなみにですが、例えばいま"test"の値が何になっているのか確認したい場合は

```haskell
getState $ "test"
```

で返ってきます(型は`IO (Maybe Value)`で、たとえば`Just 1`のように返ってきます)


# おわりに

今回紹介した手法によって、普段TidalCyclesやSuperColliderで再生している音を外部から制御できるということになります。これは結構たのしいことできそうな予感します。