クーの自由研究

冬の到来:空気が教えてくれるよ。う~んと深呼吸して、鼻がツンとしたら、それが冬のはじまり。

自由研究の準備(その7)PythonでのMIDI操作(リアルタイム編2)

おおまかな内容

引き続き、MIDIのリアルタイム操作に関連することを確認します。今回もpygame.midiを使います。

f:id:np2LKoo:20160907000652p:plain

(これは画像サンプルです。ページの下にある埋め込みがこのように表示されていれば再生ができます)

なぜやるのか・どうなると思うか

和音やベンドなどのMIDI信号はどのように扱われているのか確認します。
また、MIDI信号をイベントとして扱う方法について確認します。
内容的には目新しさはありません。すみません。
できることを自分で確認したいだけ&備忘録です。

準備と実験のやり方

和音を弾いたり、ベンド操作をして信号を受信します。
イベントの確認はサンプルに少し手をいれて実行してみます。

実験の結果

和音の確認

準備(その6)のMIDI受信プログラムで和音を受信しました。

以下のようになりました。

full midi_events:[[[144, 55, 75, 0], 11301]]
full midi_events:[[[144, 52, 83, 0], 11303]]
full midi_events:[[[144, 48, 72, 0], 11303]]
full midi_events:[[[144, 52, 0, 0], 14324]]
full midi_events:[[[144, 55, 0, 0], 14325]]
full midi_events:[[[144, 48, 0, 0], 14340]]

マニュアルには

[[[status,data1,data2,data3],timestamp],...]

の形式で受信できる感じで書いてあったので、和音はてっきり(タイムスタンプが同じなら)

[[[144, 55, 75, 0], 9999],[[144, 52, 83, 0], 9999],[[144, 48, 72, 0], 9999]]

のような形式で受信できるとおもっていたのですが、ノートONもノートOFFも単音の情報で取得できるようです。


同時に発生する音(和音)は、配列の個数が増えるような感じで情報が取得できます。

鍵盤情報以外の情報の確認

ベンドはこまかなベンド情報がやまほど流れました。

マイナス側のベンド

full midi_events:[[[224, 0, 62, 0], 1678]]
full midi_events:[[[224, 0, 61, 0], 1724]]
full midi_events:[[[224, 0, 59, 0], 1755]]
full midi_events:[[[224, 0, 57, 0], 1787]]
...

●チャンネルメッセージ
ピッチベンドチェンジ     EnH Data1  Data2
224は0xE0でチャンネル1のベンドとなります。(チャンネル1~16は16進数の末尾0からFです。)
ベンドはdata2=64が中央値のようです。Data1は使っておらず7ビットモードで動作しているようです。

プラス側のベンド

full midi_events:[[[224, 4, 66, 0], 1518]]
full midi_events:[[[224, 6, 67, 0], 1528]]
full midi_events:[[[224, 10, 69, 0], 1538]]
full midi_events:[[[224, 14, 71, 0], 1550]]

同じくベンドのプラス側ですが、こちらはData1を使っているので、14ビットモードの精度でデータを送っているようです。キーボードの設定のようです。

 

モジュレーション

full midi_events:[[[176, 1, 2, 0], 3897]]
full midi_events:[[[176, 1, 6, 0], 3914]]
full midi_events:[[[176, 1, 9, 0], 3930]]
full midi_events:[[[176, 1, 12, 0], 3954]]

176 は0xB0で、チャンネル1のコントロールチェンジ

Data1=1がモジュレーションホイールの指定で Data2がモジュレーションの強さです。

 

アフタータッチ

full midi_events:[[[144, 48, 38, 0], 3271]]
full midi_events:[[[208, 1, 0, 0], 4374]]
full midi_events:[[[208, 2, 0, 0], 4398]]
full midi_events:[[[208, 3, 0, 0], 4413]]

208は0xD0でアフタータッチ(チャンネルプレッシャ)でチャンネル1にかかります。

 

以上のようになりました。

Midi規格については

MIDI Lecture

を参考にさせて頂きました。

鍵盤情報以外のデータも問題なく受信できていることを確認しました。

イベントとタイムスタンプの確認

以下のPythonプログラムでイベントへの変換とタイムスタンプの実験をします。

(1) MidiキーボードからMidiを受信

(2) それを(pygameの)イベントに変換して送る

(3) 起動済の他のスレッドでイベントを受信する。

(4)  受信したイベントからMidi情報をつくり、タイムスタンプに一定時間を足してMidiOUTする。

# -*- coding: utf-8 -*-
import pygame.midi
import threading
import time

pygame.init()
pygame.midi.init()
pygame.fastevent.init()
event_get = pygame.fastevent.get
event_post = pygame.fastevent.post
input_id = pygame.midi.get_default_input_id()
print("input MIDI:%d" % input_id)
midi_in = pygame.midi.Input(input_id)
midi_out = pygame.midi.Output(0, latency = 1)

print ("starting")
print ("full midi_events:[[[status,data1,data2,data3],timestamp],...]")


class midiEventCheck(threading.Thread):
def __init__(self, event_get, midi_out):
super(midiEventCheck, self).__init__()
self.f_event_get = event_get
self.midi_out = midi_out
self.status = 0

def run(self):
print ("run")
self.status = 1
while self.status == 1:
events = self.f_event_get()
for e in events:
print(e)
if e.type == 34:
midi_out.write([[[e.status, e.data1, e.data2, e.data3], e.timestamp + 4200]])
time.sleep(0.001)

def stop(self):
self.status = 0

midiCheck = midiEventCheck(event_get, midi_out)
midiCheck.start()

going = True
count = 0
while going:
if midi_in.poll():
midi_events = midi_in.read(10)
print "full midi_events:" + str(midi_events)
midi_evs = pygame.midi.midis2events(midi_events, midi_in.device_id)
for m_e in midi_evs:
event_post(m_e)
count += 1
if count >= 200:
going = False
time.sleep(0.001)

midiCheck.stop()
time.sleep(1)
midi_in.close()
pygame.midi.quit()
pygame.quit()
exit()

time.sleep(0.001) (1msec)を入れているのは、ループでのCPUの負荷低減のためです。この実験程度では10~20msec sleep入れても問題ないと思います。実演奏では1msecでも短いほうがいいです。本当はループ駆動ではなく、完全なイベント駆動(事前に登録しておいて、所定のイベントが発生した場合にのみcallbackされるみたいな動作?)にしたかったのですが、これはうまくできませんでした。

以上を録音したものです。出力は録音の関係でポート0(パソコン内部標準MIDIシンセ)につなぎました。メロトロンみたいな音が最初の演奏(ポート1)で、後のピアノっぽい音がイベントを送受信して遅延指定でMIDIOUTした情報で発音された音(ポート0)です。(録音は準備(その5)で紹介したPythonプログラムで行っています。操作に手間取ったので、最初のほうは無音です。音量にご注意ください。)

pygame.midi.Output(0, latency = 1) のようにlatency = 0 以外にすれば、タイムスタンプの値をみてMidiOUTしてくれます。pygame.midi.Output(0) はデフォルトのlatency = 0 とみなされるので、タイムスタンプによらず即時出力となります。

latencyを有効にしておけば、MIDIシーケンサーPythonでも作れそうです。(シーケンサ-は処理にもたついたりしていると、タイミング制御がとても難しい感じがしていました)

pygameのイベントをさらに汎用的なWindowsイベントにしてくれればよいのですが、さすがにそれは(pygameには)なさそうでした。

まとめ

Midi関係のPythonライブラリはいろいろありますが、即時系の処理はpygameだけで問題なく処理できそうです。 

感想・思ったこと・考えたこと

いつものことながらPython&ライブラリすごいです。(こればっかり)

プログラムは実験用でmainやmainの判定を入れていません。どうもすいません。

次はスタンダードMIDIファイル(SMF)を確認したいと思います。

井の中の蛙(ボク)大海を知らず。然れどシ-を知る!

ディスニーランド&シーへ行ってきました。天気予報だと、雨だったのですが、8歳のいわゆる「高気圧少女」といっしょにいったので、ほぼ雨が降らず、晴れてとても暑かったです。事前に「なんとかして晴れさせて」とお願いしました。日曜の午前中「おなかがすいた」とぐずったときだけ雨がふり、その後ご機嫌がなおってからは快晴だったので、まさに浦安の天気と「少女」のご機嫌が連動していた感じです。シーは15周年の「まさにその日」に行けてとてもよかったです。

気象操作はマッドサイエンティストニコラ・テスラ」にはじまり、いまや軍事技術ではありますが、ちょーのーりょくによる操作も、もしかしたらありかもしれません。いまだに天気予報が(たまに)大外れするのはそのせいでしょうか?(ボクの秘密なのですが、実はUFOやUMAやオカルトが大好きで、マッドサイエンティストに憧れています。誰にも言わないでください。)脱線しました。どうもすいません。

「少女」はジブンでちょのーりょくを使っているのではなく、うちゅーのなにかにお願いしている、または一時的になにかをつないでいるようなので、Lifeを削るようなことはありません。ご安心ください。