今日は楽器で演奏したデータをシンセサイザーに中継するプログラムを書いてみようと思う。
>>> import javax.sound.midi.MidiSystem as MS
>>> MS.getMidiDeviceInfo()
array([YAMAHA USB IN 0-1, Microsoft MIDI ?}?b?p?[, YAMAHA XG WDM SoftSynthesizer
, Microsoft GS Wavetable SW Synth, YAMAHA USB OUT 0-1, Real Time Sequencer, Java
Sound Synthesizer], javax.sound.midi.MidiDevice$Info)
>>> ds = _
>>> midiIn = ds[0]
>>> ds[2]
YAMAHA XG WDM SoftSynthesizer
>>> midiOut = _
入口と出口は取得できた。
しかしリフレクションでメソッドを眺めても次に呼ぶべきメソッドがわからない。
あ、そうか、MidiSystemを使うのか。
>>> MS.getMidiDevice(midiIn)
com.sun.media.sound.MidiInDevice@473f4c
>>> midiIn = _
>>> midiOut = MS.getMidiDevice(midiOut)
getMidiDeviceInfoで取得できるのはあくまでデバイスの情報だけであって、
いろいろなメソッドは
getMidiDeviceで取得したMidiDeviceクラスのオブジェクトが持っている。
さて、デバイスは取れた。
楽器を演奏すると外部の機器からMIDIメッセージが送られてくる、と。
それを取得する方法はまぁ順当に考えてリスナを登録してコールバックしてもらう方法だろう、と。
>>> midiIn.receiver
javax.sound.midi.MidiUnavailableException: MIDI IN receiver not available
(中略)
>>> midiIn.transmitter
com.sun.media.sound.MidiInDevice$MidiInTransmitter@3b6dbd
>>> midiInTrans = _
レシーバーじゃなくてトランスミッタを取得するんだそうな。
インタフェース Transmitter
。
インタフェース Receiver
。
レシーバを作成してこのトランスミッタに登録すればよさそう。
>>> from javax.sound.midi import *
>>> class MyRecv(Receiver):
... def send(self, msg, time):
... print msg, time
...
>>> midiInTrans.receiver = MyRecv()
何も起きない…。
>>> midiIn.isOpen()
0
デバイスを開いていないじゃん!w
>>> midiIn.open()
>>> com.sun.media.sound.FastShortMessage@18430a5 4293265700000
お、メッセージが表示された。
でもこの一つだけで、楽器を操作しても続きが表示されない…。
やりなおし。
デバイスを開いてからレシーバを登録する方針で。
>>> from javax.sound.midi import *
>>> MidiSystem.getMidiDeviceInfo()
array([YAMAHA USB IN 0-1, Microsoft MIDI ?}?b?p?[, YAMAHA XG WDM SoftSynthesizer
, Microsoft GS Wavetable SW Synth, YAMAHA USB OUT 0-1, Real Time Sequencer, Java
Sound Synthesizer], javax.sound.midi.MidiDevice$Info)
>>> midiIn = _[0]
>>> midiIn = MidiSystem.getMidiDevice(midiIn)
>>> midiIn.open()
>>> class MyRecv(Receiver):
... def send(self, msg, time):
... print ",",
...
>>> midiIn.transmitter.receiver = MyRecv()
>>> , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
, , ,
おー、定期的にメッセージが送られてきている。
楽器を操作すると一気にたくさん表示される。
とりあえずこれをどうしようか。
>>> from javax.sound.midi import *
>>> MidiSystem.getMidiDeviceInfo()
array([YAMAHA USB IN 0-1, Microsoft MIDI ?}?b?p?[, YAMAHA XG WDM SoftSynthesizer
, Microsoft GS Wavetable SW Synth, YAMAHA USB OUT 0-1, Real Time Sequencer, Java
Sound Synthesizer], javax.sound.midi.MidiDevice$Info)
>>> midiIn = _[0]
>>> midiOut = _[2]
>>> midiIn, midiOut = map(MidiSystem.getMidiDevice, (midiIn, midiOut))
>>> [x.open() for x in (midiIn, midiOut)]
[None, None]
>>> midiOut.receiver
com.sun.media.sound.MidiOutDevice$MidiOutReceiver@186d315
>>> midiOutRecv = _
>>> class MyRecv(Receiver):
... def send(self, msg, time):
... midiOutRecv.send(msg, time)
...
>>> midiIn.transmitter.receiver = MyRecv()
おー、音が鳴った!
第 15 章: MIDI サービスの提供
>>> mySeq = Sequence(Sequence.SMPTE_24, 48)
>>> mySeq.tracks
[]
>>> mySeq.createTrack()
javax.sound.midi.Track@1feab48
>>> mySeq.tracks
[javax.sound.midi.Track@1feab48]
>>> myTrack = _[0]
>>> class MyRecv(Receiver):
... def send(self, msg, time):
... myTrack.add(MidiEvent(msg, time))
...
>>> midiIn.transmitter.receiver = MyRecv()
>>> myTrack.size()
163
>>> myTrack.size()
565
演奏するとトラックのサイズが増える。
記録できてそう。
>>> MidiSystem.getMidiFileTypes(mySeq)
array([1, 0], int)
>>> import java.io.File as File
>>> MidiSystem.write(mySeq, 1, File(r"c:\foo.mid"))
2129
>>> midiIn.close()
>>> midiOut.close()
MidiSystemのwriteを使ったらMIDIファイルができた。
とりあえず再生してみる。…なんか期待していたものと違う。
しかもなぜかFastShortMessageのキャストエラーで保存に失敗したりする。
とりあえずメッセージをリストに保存しておいて見てみることにした。
>>> [hex(m[0].command) for m in messages[-20:]]
['0xe0', '0xe0', '0xb0', '0xb0', '0xb0', '0xe0', '0xe0', '0xe0', '0xb0', '0xb0',
'0xb0', '0xe0', '0xe0', '0xb0', '0xe0', '0xb0', '0xb0', '0xe0', '0xe0', '0xb0']
コントロールチェンジとピッチベンドチェンジがたくさん来ている…。
>>> foo = []
>>> for m in messages:
... com = hex(m[0].command)
... if not com in foo: foo.append(com)
...
>>> len(foo)
4
>>> foo
['0xf0', '0x90', '0xe0', '0xb0']
その他はノートオンとエクスクルーシブだけみたいだ。
特に不審なものは…あっ、そうか、わかった。
最初に保存に成功したときにはウダーの初期化をしなかったけど、
失敗した二回目には初期化をしたんだった。
で、今回もただのテストだからと初期化をしなかったんだ。
初期化の時にはSysexMessageが送られるはずだ。
FastShortMessage「の」キャストに失敗したんじゃなくて、
SysexMessageのFastShortMessageへのキャストに失敗したんだな、きっと。
むむむ、違うのか?
わからなくなってきた。
ドキュメントもないし…。
ソースコードを読もう。
ソース無かったorz。
com.sun.media.sound.StandardMidiFileWriter。
とりあえずウダーを初期化しなかった場合はMIDIファイルの出力は成功する。
でも実際の演奏よりかなり間延びをした記録のされ方をしている。
=
あうー。
毎回ウダーのケーブルを抜き差しして初期化するのが面倒なので、
ソフトウェア的に初期化できるようにしようと、
ウダーが初期化するときに送っているメッセージをそのまま
ソフトウェア的に送ってみたんだが、期待通りの結果にならない。
ピッチベンドがどうなっているのかを出力させながら演奏してみた。
普段は8192くらいの値にいて、音をずらしていくと値が上がっていく。
ただ、てっきり隣の音の領域に入ったら0になるんだと思っていたら、
ずいずい上がって行くよこれ。
あー。
ピッチベンドの仕組み
で説明されているとおりのメッセージが送られている。
1オクターブピッチベンドで移動できるのか…。
=
どうも、グリッサンドしたときにはずっと同じ音符のようだ。
「ド」の鍵盤を押したときに、「ド」になるときと
「ド#」の低めのピッチベンドになるときとの違いがよくわからない。
もう疲れた。
=
走ってきた。
=
http://la.ma.la/misc/js/setclipboard.txtを使って、
リンクを貼りたいページで起動するとタイトルとURLからAタグを作ってクリップボードに入れてくれる
ブックマークレットを作って使っていたのだけど、今日は全然動かない。
なんでだろう…?
と考えて今やっと気がついた。
今日再起動したときにFLASHのアップデートが入った記憶が…。
ぁぅぁぅ。
なんかいい方法ないもんかなぁ。
署名付きJavaアプレットだったらクリップボードにアクセスできるかな?
それなら自分のサーバに自分の署名したアプレットをおけばいいのかな?
ActionScriptを勉強して自分で新しいバージョンのを作るのと
Javaアプレットで作るのとどっちが早いだろう。
=
今日のまとめ。
Jythonなら24行で「MIDI楽器から入ってきたMIDI信号を
ソフトウェアシンセサイザに中継するプログラム」が書ける。
ただし今のところMIDI機器が決めうち。
ウダー練習ソフトでは、MIDIの中継と一緒に画面の描画処理もしているが、
こちらは単純に中継するだけなので負荷が軽い。
前者は僕のマシンでCPU使用率100%だけど、後者は50%くらい。
負荷が軽いので遅延も起こりにくいはず。
もっともウダー練習ソフトのほうでも優先度を最大限に上げれば
遅延は起こらないように思えるけども。
ピッチベンドを使うと
上下1オクターブのグリッサンドが可能な上に、
音の解像度は半音の600分の1。
MIDIの生演奏の保存は思ったほど簡単ではなかった。
トラックの解像度の指定とかを適当にしたのがよくない。
あとSysexが悪さをしていると思うのだけど情報不足。
走ると膝が痛い。
いい運動靴を買うべきか。
ナイキの。
センサーが入っていてiPodと通信するアレ。
買おうかなぁ、本当に。
リンクを貼るブックマークレットが動かなくなったのがすごく不便。