🎼

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

.

ここに書いてある通りで関数合成です。以下のように使えます。revstut 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") -- これと等価

もう少し凝った用例は関数オブジェクトを定義する以下のような方法です。
revstut 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

もし〜だったら〜する。それ以外は〜のようなものです。
以下はwantAmenTrueの場合はnumbersamenが同時再生されます。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