Haskell Advent Calendar 2011 20日目は、Haskoreというライブラリの紹介です。
Haskoreとは?
Haskellによって書かれた音楽を扱うためのライブラリです。CSound、MIDI、SuperColliderに対応しています。
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と入力することで聴けます。
まとめ
すごく使いやすい、というほどではないのですが、音楽の実験をするときはなかなか便利でおもしろいツールなんじゃないかと思います。
自動で作曲や曲のアレンジができたらおもしろそうだなぁ…