TidalCyclesで使えそうなHaskellの操作
はじめに
私はHaskellのプライベートおよび業務での使用経験があるのですが(開発例)、TidalCyclesを知ったのはつい最近でTutorialを全部実行してみた程度です。しかしTidalCyclesはHaskellのDSLであり仕様はhackageを見ればある程度Haskellの知識でわかるので、Haskell経験者から見たTidalCyclesのHaskell寄りな操作をまとめてみました。といってもDSLとしてかなりの量で便利な関数が用意されているので使う箇所は限られると思います。
環境
name | info |
---|---|
PC | Mac mini 2023 M2 Pro 32GB RAM |
OS | Sequoia 15.6 |
SuperCollider | 3.13.0 |
SuperDart | v1.7.3 |
sc3-plugins | 3.13.0 |
TidalCycles | 1.10.1 |
操作
let
変数としての定義です。Tutorialにも地味に登場します。
以下のようなことができます。
let nRun4 = n $ run 4
let brokendrum = sound "amencutup*8"
# cat[nRun4, n (choose [0..7])]
d1 brokendrum
.
ここに書いてある通りで関数合成です。以下のように使えます。rev
とstut 4 0.25 0.25
を合成しています。
d1 $ rev . stut 4 0.25 0.25 $ sound "numbers"
d1 $ rev (stut 4 0.25 0.25 $ sound "numbers") -- これと等価
もう少し凝った用例は関数オブジェクトを定義する以下のような方法です。
rev
とstut 4 0.25 0.25
とさらにsound
を合成することで高機能な関数f
を定義しています。
let f = rev . stut 4 0.25 0.25 . sound
d1 $ f "numbers"
d1
も関数なので以下のようにもできます。
let d1f = d1 . rev . stut 4 0.25 0.25 . sound
d1f "numbers"
<>
2パターンを同時再生させる二項演算であり、以下が等価となります。
d1 $ sound "numbers" <> sound "hh"
d1 $ sound ("numbers" <> "hh")
d1 $ sound "numbers,hh"
d1 $ stack [sound "numbers", sound "hh"]
以下のように一旦変数に保存しておいて使いまわしたりに便利そうです。
let amen = sound "amencutup*8"
# fastCat [n $ run 4, n (choose [0..22])]
let num = stut 4 0.25 0.25 $ sound "numbers"
d1 $ num <> amen
map
値のリストに関数を適用します。
以下はリストを1サイクルのパターンにする関数を作成して適用することにより1,2,3,4を再生します。
let nums = [1..4] :: [Int]
let getNotePat = n . fastCat . map toEnum
d1 $ s "hh" <> s "numbers*4" # getNotePat nums
mempty
何も無いゼロのようなパターンです。後述のif
などで効力を発揮します。
d1 $ mempty -- 無音
d1 $ mempty <> amen -- 先述のamenのみが再生
if then else
もし〜だったら〜する。それ以外は〜
のようなものです。
以下はwantAmen
がTrue
の場合はnumbers
とamen
が同時再生されます。False
の場合はnumbers
のみの再生です。
let wantAmen = True
d1 $ sound "numbers" <> if wantAmen then amen else mempty
case of
if then else
の複数分岐版です。以下はplayNum
の数値次第で再生音源が変わります。
let playNum = 0
d1 $ sound $ case playNum of
0 -> "numbers:0"
1 -> "numbers:1"
2 -> "numbers:2"
_ -> "hh"
<$> <&>
関数をパターンに適用します。以下は2を5に変えるtwoToFive
を用いています。以下2つのd1
は元は2を再生していたのに対し、twoToFive
により5に変更されます。
let twoToFive x = if x == 2 then 5 else x
d1 $ sound "hh"
<> sound "numbers*4"
# n (twoToFive <$> choose [1..4])
d1 $ sound "hh"
<> sound "numbers*4"
# n (twoToFive <$> "1 2 3 4")
以下のようにパターンの文字列連結にも使えます。
d1 $ sound ((++ "ers") <$> "numb")
<$>
の場合右辺のパターンに対して関数を適用しますが、Data.Functor
をインポートすれば関係を逆にして<&>
で左辺のものに対して右辺のものを適用します。
import Data.Functor
d1 $ sound ("numb" <&> (++ "ers"))
pure
""
のダブルクォーテーション囲みはHaskellでは文字列ですが、TidalCyclesではパターンとしての扱いとなります。これをpure
適用対象は文字列ということにして、返り値としてパターンを返します。以下では++
で文字列結合しています。
d1 $ sound . pure $ "numb" ++ "ers" ++ ":1"
d1 $ sound $ "numb" |++| "ers" -- 等価
<*>
右辺のパターンに対し左辺の関数のパターンを適用し、結果をパターンとして出力します。
以下は、1と3がランダムで再生されます。
d1 $ s "numbers*4"
# n ((choose [(+1), (+3)]) <*> "0*4")
>>=
左辺のパターン中の値を取り出し、右辺の値を引数としてパターンを出力する関数に適用します。
以下は、1と3がランダムで再生されます。
d1 $ s "numbers*4"
# n ("0*4" >>= (\x -> choose [(x+1), (x+3)]) )
do記法
パターン内部の値を取り出した平易な記述を可能とします。min
はHaskellに元からある関数です。
以下は1サイクルで1,2,8,8を再生します。サイクルの分割内の個別の内部操作を記述していると考えれば良いかと思います。
let noteDo = do
x <- "1 2 3 4"
h <- "0 5"
let r = min 8 $ x + h
pure r
d1 $ s "numbers*4" # n noteDo # speed 1.5
なおmin
はTidalCyclesでも同名で定義があるので結局以下と等価です。
d1 $ s "numbers*4" # n (min 8 $ "1 2 3 4" + "0 5") # speed 1.5
>=>
値を引数としてパターンを返す関数2つを合成します。
以下は、2と6がランダムで再生されます。
let f1 x = choose [(x+1), (x+3)]
let f2 x = pure $ x * 2
let f = f1 >=> f2
d1 $ s "numbers*4" # n ("0*4" >>= f)
おわりに
上記調査した感じ、まだまだTidalCyclesに活かせるHaskellの操作はありそうだと思いました。今後もアップデートしていきます。
Discussion