Haskell Advent Calendar 2011 20日目

Haskell Advent Calendar 2011 20日目は、Haskoreというライブラリの紹介です。

Haskoreとは?

Haskellによって書かれた音楽を扱うためのライブラリです。CSoundMIDISuperColliderに対応しています。
MIDIを使用するものとして話を進めていきます(他の二つは使い方がよくわかっていない)。

インストール方法

$ cabal install haskore

はい…これだけです。結構時間かかります。

midiの再生に必要になるのでTimidityも入れておきましょう。

# apt-get install timidity

早速試してみよう。

$ ghci
GHCi, version 7.0.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> import Haskore.Interface.MIDI.Render
Prelude (省略) > import Haskore.Music.GeneralMIDI as GM
Prelude (省略) > import Haskore.Melody.Standard
Prelude (省略) > playTimidity $ GM.fromMelodyNullAttr GM.AcousticGrandPiano $ line [c 1 qn (), e 1 qn (), g 1 qn ()]

BPM60でド、ミ、ソの音が出れば成功です。

使い方

関連モジュールをインポートして、ひたすら音を並べて、+:+で直列につなげて、=:=で並列につなげて、演奏する。それだけ。

Haskoreのソースのsrc/Haskore/Example/WhiteChristmas.hsは簡単な例。

あるアルゴリズムにしたがって作曲するという用途が一番適していると思います(ただ打ち込むだけなら他のDTMソフトのほうが効率がよい)。最近ホットなのはid:aikeさんの竹内関数で音楽生成とか。

適当なモジュールの解説



Haskore.Melody

音階、長さからノートを生成する関数(c, cs, …, bf, b)が定義されている。

Haskore.Music

休符やメロディ同士の合成、メロディの演算が定義されている。

Haskore.Music.GeneralMIDI

上の内容の他に、音符の長さ、楽器、ニュアンス(クレッシェンドとか)などが定義されている。

Haskore.Composition.Drum

ドラムの数々、音楽に組み込むための関数など。

Haskore.Composition.Chord

和音を生成するための関数群。

Haskore.Interface.MIDI.Render

MIDIファイルにするためのインターフェイス。

サンプル


>.<演算子がちょっとトリッキーなので注意。音階とリズムからメロディを生成する演算子です。例:[1,2,3] >.< [qn,qn,hn]→ミーファーソーー

import qualified Haskore.Composition.Drum  as Drum
import qualified Haskore.Composition.Chord as Chord

import           Haskore.Basic.Dynamics (Velocity)
import           Haskore.Melody.Standard   as Melody
import           Haskore.Music.GeneralMIDI as MidiMusic
import qualified Haskore.Music             as Music

import qualified Haskore.Melody

import           Haskore.Basic.Pitch (Class(..))
import Haskore.Interface.MIDI.Render
import qualified Data.Accessor.Basic as Accessor

infixl 8 >.<

tone = [d 1, e 1, fs 1, g 1, a 1, b 1, c 2, d 2, e 2, f 2]

vel :: Velocity -> NoteAttributes
vel vl = Accessor.set Melody.velocity1 vl Melody.na

unitQ = replicate 4 en

rhythmA = unitQ ++ [dqn, en]
phraseAl = [0,5,4,3]
phraseAr = [0,0]

rhythmB = unitQ ++ [hn]
phraseB = [7,7,6,4,5]

rhythmC = unitQ
phraseC = replicate 4 7

rhythmD = unitQ ++ [dqn]
phraseD = [8,7,6,4,3]

rhythmE = [en,en,qn]
phraseE = replicate 3 5

rhythmF = [en,en,den,sn,dqn]
phraseF = [5,7,3,4,5]

rhythmG = [en,en,den,sn,en,en,en,en]
phraseG = replicate 5 6 ++ replicate 3 5

rhythmH = unitQ ++ [qn,qn]
phraseH = [5,4,4,3,4,7]

rhythmI = unitQ ++ [qn]
phraseI = [7,7,6,4,3]

v = vel 0.75

chords = [
    Chord.generic G Chord.majorInt wn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic C Chord.majorInt hn v
    ,Chord.generic C Chord.majorInt hn v
    ,Chord.generic D Chord.majorInt hn v
    ,Chord.generic D Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt wn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic C Chord.majorInt hn v
    ,Chord.generic C Chord.majorInt hn v
    ,Chord.generic Gs Chord.minorInt hn v
    ,Chord.generic A Chord.minorSeventhInt qn v
    ,Chord.generic D Chord.majorSeventhInt qn v
    ,Chord.generic G Chord.majorInt qn v
    ,Chord.generic D Chord.majorSeventhInt qn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic C Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic A Chord.majorInt hn v
    ,Chord.generic D Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic C Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ,Chord.generic C Chord.majorInt hn v
    ,Chord.generic G Chord.majorInt hn v
    ]
acc = line $ map chord $ Chord.leastVaryingInversions ((1,C),(1,C)) chords
    
m >.< r = foldl1 (+:+) $ map ($()) $ zipWith ($) (map (tone!!) m) r

melodyA = (phraseAl ++ phraseAr) >.< rhythmA +:+
    (phraseAl ++ map (+1) phraseAr) >.< rhythmA

melodyB = phraseE >.< rhythmE
    +:+ phraseE >.< rhythmE
    +:+ phraseF >.< rhythmF
    +:+ enr
    +:+ phraseG >.< rhythmG

patternABAC a b c = a +:+ b +:+ a +:+ c

melody = melodyA
    +:+ map (+1) (phraseAl ++ map (+1) phraseAr) >.< rhythmA
    +:+ phraseB >.< rhythmB
    +:+ melodyA
    +:+ map (+1) phraseAl >.< unitQ
    +:+ phraseC >.< rhythmC
    +:+ phraseD >.< rhythmD
    +:+ enr
    +:+ patternABAC melodyB (phraseH >.< rhythmH) (phraseI >.< rhythmI)

drumA0 d = Drum.toMusic AcousticBassDrum d (vel 1.0)
drumA1 d = Drum.toMusic Tambourine d (vel 1.0)
crash d = Drum.toMusic CrashCymbal1 d (vel 1.0)

drumA = line [drumA0 en, drumA1 en]
drumC = line $ replicate 7 wnr ++ replicate 6 enr ++ [crash en]

song = Music.changeTempo 2.0 $ -- BPM 120
    MidiMusic.fromMelodyNullAttr MidiMusic.ElectricPiano1 melody
    =:= MidiMusic.fromStdMelody MidiMusic.ElectricBassFinger acc
    =:= Music.line (replicate 64 drumA) =:= drumC

何の曲かは、自分で聴いてお確かめください。
ghciから:loadし、playTimidity songと入力することで聴けます。

まとめ


すごく使いやすい、というほどではないのですが、音楽の実験をするときはなかなか便利でおもしろいツールなんじゃないかと思います。

自動で作曲や曲のアレンジができたらおもしろそうだなぁ…