GaucheからMIDI、内臓シンセサイザーを鳴らすことを検討しています。仕組みを調べたこと、仕様についての頭の整理に場所をお借りします。
MIDI関連のAPIはMMSystemで定義されている。音を出すにはMIDIをopenし、発音メッセージを送り、消音メッセージを送り、MIDIをCloseする。
それぞれ midiOutOpen , midOutShortMsg, midOutShortMsg ,midOutClose が対応する。
簡易グラフィクスに連結するためにSchemeの厳密数を非厳密数に変換して受け取りDelphi命令を発する仕組みは用意されているので、ここにMIDI関連の命令を追加する。
@MIDIOpen @MIDIMsg @MIDIClose
4バイトを送信することになっている。簡易グラフィクスの仕組みと同じ方法で4バイトをMIDIに送る。GraphicsメニューのopenでMIDIもオープン。closeでMIDIもクローズ。
ライブラリは画像処理も合わせてGaucheのライブラリとして整理。画像と音とを連動させて簡易な音楽+画像のお遊びツールにする。
(voice n) nで楽器を指定する。MIDIMsgで送る。
(dempo n) で与える。デフォルトは144くらいかな。
用意した手続き
(midi-open) MIDIディバスオープン
(midi-close) クローズ。
(midi-msg x1 x2 x3 x4) MIDIにShortメッセージを送る。
(voice n) 音色を楽器番号nに指定
(tempo n) テンポのnに指定 デフォルト ♪=144
(volume n) 音の大きさをnに指定
nは0 < n < 127
(channel n) チャンネルを設定0~16
(note p :l length :b bend) 単音を鳴らす。 P 音程 length 長さ bend ベンドの範囲。 音程はC0~ C4が中央のド。長さのデフォルトは1/4で四分音符。 音程をCのように省略した場合にはC4。 ベンドは-12~+12 マイナスの場合にはチョークアップしゃくりあげ。プラスの場合にはチョークダウン。
(chord p :l length :a pattern) 和音を鳴らす。 pは音程のリストで与える。例 (C4 E4 G4) length は鳴らす長さ。1で1小節。デフォルトは1. patternはアルペジオパターン。(1 2 3 2) (1 3 2 1) など。パターン省略の場合には全音符。
(pitch? p) pがピッチを表すシンボルかどうか? #t#f A#4は#t、A4#は#f。
(pitch->number p) 音程pをMIDI音階の自然数に変換。
(number->pitch n) MIDI音階の自然数nを音程表記のシンボルに変換。
(note-on n) 発音。単純にMIDI音階の自然数nを鳴らす。鳴りっぱなし。
(note-off n) 消音。単純にMIDI音階の自然数nを消音する。
(all-note-off) MIDIのモード。オールノートオフ。
(all-sound-off) MIDIのモード。強制的に音源を消音。
(reset-all-controller) MIDI音源の初期化。
(pitch-bend n) nだけピッチベンドする。 nは-8192~+8192。
(pitch-bend-sensitivity n) n半音の範囲をベンドの範囲とする。
(use kids)
(voice 93)
(tempo 240)
(volume 120)
(define (cde)
(note 'C)
(note 'C#)
(note 'D)
(note 'D#)
(note 'E)
(note 'F)
(note 'F#)
(note 'G)
(note 'G#)
(note 'A)
(note 'A#)
(note 'B)
(note 'C5))
(define (tak x y z n)
(let ((esc '()))
(define (tak1 x y z n)
(chord (map tak->pitch (list x y z)) :a '(1 2 3 2 1 2 3 2))
(cond ((zero? n) (esc))
((<= x y) y)
(else (tak1 (tak1 (- x 1) y z (- n 1))
(tak1 (- y 1) z x (- n 1))
(tak1 (- z 1) x y (- n 1))
(- n 1)))))
(call/cc (lambda (c)
(set! esc c)
(tak1 x y z n)))))
(define (tak->pitch n)
(case n
((-1) 'D5)
((0) 'E5)
((1) 'F5)
((2) 'G5)
((3) 'A5)
((4) 'B5)
((5) 'C6)
((6) 'D6)
((7) 'E6)
((8) 'F6)
((9) 'G6)
((10) 'A6)
((11) 'B6)
((12) 'C7)))
MIDI対応のBabbage VER0.7とMIDI&グラフィクスライブラリをアップしました。
http://homepage1.nifty.com/~skz/Entry/music.html
コラッツ予想の経過数を(mod 14)にして2オクターブのtakのときのドリアンスケールで鳴らしてみた。ちょっとお遊びでベンドをかけてやるとあら不思議。マイナスは出ないからフリージアンだね。
(define (tak->pitch n)
(case n
((-1) 'D5)
((0) 'E5)
((1) 'F5)
((2) 'G5)
((3) 'A5)
((4) 'B5)
((5) 'C6)
((6) 'D6)
((7) 'E6)
((8) 'F6)
((9) 'G6)
((10) 'A6)
((11) 'B6)
((12) 'C7)
((13) 'D7)
((14) 'E7)))
(define (f x)
(if (even? x)
(/ x 2)
(+ (* 3 x) 1)))
(define (g x)
(note (tak->pitch (modulo x 14)) :b 1)
(if (= (f x) 1)
'end
(g (f x))))
地元の図書館にお医者さんが自費出版で出したらしい本があります。πをメロディーとみて鳴らすというものです。ネットにも同様に次のような例があります。前から試してみたかったので再現してみました。
http://web.kyoto-inet.or.jp/people/hase_314/pi/pai.htm
気のせいかもしれませんが、海の波の音と同様、癒される感じがします。
;;有理数m/nを有限桁実数としてリストに変換する。
(define (rational->real r l)
(let ((n (numerator r))
(d (denominator r)))
(rational->real1 (modulo n d) d l (list (quotient n d)))))
(define (rational->real1 n d l ls)
(if (zero? l)
(reverse ls)
(rational->real1 (modulo (* 10 n) d)
d
(- l 1)
(cons (quotient (* 10 n) d) ls))))
;;グレゴリ級数によりtan(-1)を計算。
;;各桁の数が必要なだけならばspigot algorithmが効率がよさそう。
;;いずれ書き直し。
(define (arctan x n)
(arctan1 x n 1))
(define (arctan1 x n m)
(if (< n m)
0
(let ((sign (if (even? m) -1 1)))
(+ (* sign (/ 1 (+ (* 2 (- m 1)) 1)) (expt x (+ (* 2 (- m 1)) 1)))
(arctan1 x n (+ m 1))))))
;;マチンの公式により第n項まで級数計算。
(define (machin n)
(* 4 (- (* 4 (arctan 1/5 n)) (arctan 1/239 n))))
;;音をC調に変換
(define (number->c-ionian n)
(case n
((1) 'C5)
((2) 'D5)
((3) 'E5)
((4) 'F5)
((5) 'G5)
((6) 'A5)
((7) 'B5)
((8) 'C6)
((9) 'D6)
((0) 'B4)))
;;πのメロディーを鳴らす。
(define (pi-melody n)
(let ((ls (rational->real (machin 300) n)))
(for-each note (map number->c-ionian ls))))