17
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ClojureAdvent Calendar 2013

Day 17

MIDI と竹内関数

Last updated at Posted at 2013-12-16

javax.sound.midi を使って MIDI を鳴らします

もう少し正確に言うと, OS の方で設定されている MIDI 音源に MIDI メッセージを送ります.
適当な MIDI ファイル (拡張子 .mid) を探してきて, ダブルクリックで鳴るようようなら, 鳴るでしょう.

デフォルトのシンセサイザーで, チャンネル 0 番に, 音色デフォルト (たぶんピアノになっているでしょう) で,
C4 (ノート番号 48) の音を, ベロシティ 64 で, 1000ミリ秒鳴らすには,

(with-open [synth (doto (javax.sound.midi.MidiSystem/getSynthesizer) .open)]
  (let [channel (aget (.getChannels synth) 0)]
    (.noteOn channel 48 64)
    (Thread/sleep 1000)
    (.noteOff channel 48 64)))

とします.

MIDI メッセージは, 元々は楽器の操作を転送するものなので, 「何秒間鳴らす」というメッセージではなく,
「打鍵した」というメッセージ (note on) と「離鍵した」というメッセージ (note off) を,
適当なタイミングで送ることで, 音を鳴らしたり止めたりします.

ノート番号は 0~127 で半音単位で指定します. 48 が C4 (真ん中のド) です.
どんな音律で演奏するかは, 音源側に委ねられますが, 普通は何も設定しなければ 12平均律になっているでしょう.

ベロシティは「速度」という意味ですが, これも, 打鍵の速さを情報として送るところから来ています.
大抵の音源は, この打鍵速度の情報に基づいて音量を決めますので, さしあたり音量と考えても良いと思います.
0~127 で指定します.

もう少し高レイヤーが好みの方は, 便利なライブラリが沢山ありますので, そちらをどうぞ.
「俺が MIDI ソフトを作る」という低レイヤーの好きな方はいろいろ試してみてください.

以上です. というのもなんなのでもう少し.

竹内関数の引数の変遷を遅延シーケンスに

LISPer 大好き竹内関数です.

(defn tarai [x y z]
  (if (<= x y) y
    (tarai (tarai (dec x) y z)
           (tarai (dec y) z x)
           (tarai (dec z) x y))))

説明要らないですね.
さて, この竹内関数が再帰的に呼び出されている時の引数を, たらい回されている最中に知りたいとします.

(defn tarai [x y z]
  (println x y z)
  (if (<= x y) y
    (tarai (tarai (dec x) y z)
           (tarai (dec y) z x)
           (tarai (dec z) x y))))
user=> (tarai 2 1 0)
2 1 0
1 1 0
0 0 2
-1 2 1
1 0 2
0 0 2
-1 2 1
1 1 0
0 2 1
2

見る事はできますが, 使えないですね.

ちょっと手を加えて, 左の引数から順に正格評価されると仮定した場合に,
この関数が呼ばれた時の引数を遅延シーケンスで得られるようにしましょう.

(defn args-and-result [x y z]
  (if (<= x y) [[[x y z]] y]
    (let [[a0 r0] (args-and-result (dec x) y z)
          [a1 r1] (args-and-result (dec y) z x)
          [a2 r2] (args-and-result (dec z) x y)
          [a3 r3] (args-and-result r0 r1 r2)]
      [(cons [x y z] (lazy-seq (concat a0 a1 a2 a3))) r3])))

(defn args [x y z] (first (args-and-result x y z)))

args 関数に竹内関数の引数を与えると, 3つの引数をベクタにしたものの遅延シーケンスが得られます.

user=> (args 2 1 0)
([2 1 0] [1 1 0] [0 0 2] [-1 2 1] [1 0 2] [0 0 2] [-1 2 1] [1 1 0] [0 2 1])

また

(defn view-args [x y z t]
  (doseq [xyz (args x y z)]
    (println xyz)
    (Thread/sleep t)))

などとすれば t ミリ秒毎に, 引数の変遷を見る事ができます.

user=> (view-args 2 1 0 500)
[2 1 0]
[1 1 0]
[0 0 2]
[-1 2 1]
[1 0 2]
[0 0 2]
[-1 2 1]
[1 1 0]
[0 2 1]
nil

竹内関数で音楽生成

さて, 竹内関数で音楽生成 という素敵なエントリをご存知でしょうか?
もしご存知でなければ, 是非ご覧に(お聞きに)なった上で, ここに戻ってきてください.

aike さんによる音楽生成を簡易に再現してみましょう.
ここでは, 和音を順番に鳴らすにとどめ, 自動アルペジオ機能はみなさんへの課題とします.

; 演奏中に音名も表示したいので,
(defn nn2name [nn]
  ({0 "c"   1 "cis" 2 "d"   3 "es"  4 "e"   5 "f"
    6 "fis" 7 "g"   8 "as"  9 "a"  10 "b"  11 "h"}
   (mod nn 12)))

; たらい回しの各回で, 引数を表示し且つ, arg2nn で指定された「引数 -> ノート番号」の対応に従い,
; t ミリ秒間, 和音を鳴らします.
(defn listen-args [x y z arg2nn t]
  (with-open [synth (doto (javax.sound.midi.MidiSystem/getSynthesizer) .open)]
    (let [channel (aget (.getChannels synth) 0)]
      (doseq [xyz (args x y z)]
        (println (apply str
                   (mapcat #(let [nn (arg2nn %)]
                              [% " -> (" (nn2name nn) "[" nn "])\t"]) xyz)))
        (dorun (map #(.noteOn channel (arg2nn %) 64) xyz))
        (Thread/sleep t)
        (dorun (map #(.noteOff channel (arg2nn %) 64) xyz))))))

; aike さんの割当: 0 を E4 として, ハ長調のダイアトニックトーンに割り当てます.
(def e-ph
  {-2 48 -1 50 0 52 1 53 2 55 3 57 4 59 5 60 6 62 7 64 8 65 9 67 10 69})

; 別の割当も試してみましょう.
(def c-io
  {-2 45 -1 47 0 48 1 50 2 52 3 53 4 55 5 57 6 59 7 60 8 62 9 64 10 65})
user=> (listen-args 2 1 0 c-io 1200)
2 -> (e[52])	1 -> (d[50])	0 -> (c[48])	
1 -> (d[50])	1 -> (d[50])	0 -> (c[48])	
0 -> (c[48])	0 -> (c[48])	2 -> (e[52])	
-1 -> (h[47])	2 -> (e[52])	1 -> (d[50])	
1 -> (d[50])	0 -> (c[48])	2 -> (e[52])	
0 -> (c[48])	0 -> (c[48])	2 -> (e[52])	
-1 -> (h[47])	2 -> (e[52])	1 -> (d[50])	
1 -> (d[50])	1 -> (d[50])	0 -> (c[48])	
0 -> (c[48])	2 -> (e[52])	1 -> (d[50])	
nil

音名で表示されている和音が演奏されましたか?

user=> (listen-args 10 5 0 e-ph 1200)
10 -> (a[69])	5 -> (c[60])	0 -> (e[52])	
9 -> (g[67])	5 -> (c[60])	0 -> (e[52])	
8 -> (f[65])	5 -> (c[60])	0 -> (e[52])	
7 -> (e[64])	5 -> (c[60])	0 -> (e[52])	
6 -> (d[62])	5 -> (c[60])	0 -> (e[52])	
5 -> (c[60])	5 -> (c[60])	0 -> (e[52])	
4 -> (h[59])	0 -> (e[52])	6 -> (d[62])	
3 -> (a[57])	0 -> (e[52])	6 -> (d[62])	
2 -> (g[55])	0 -> (e[52])	6 -> (d[62])	
...

とすると aike さんの動画と同じ和音の変遷が聞けます. 343,073 小節あるので気をつけてください.

クリスマス関係なくてすみません.
core.async も使ってなくてすみません.

17
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?