#はじめに
先日、『PowerShellで演奏する電子オルゴール』の記事を書きましたが、同様の処理をpython3に移植してみました。
関連記事)
Golangで演奏する電子オルゴール
GitHubにて、ソースコードおよびサンプルの楽譜データも公開しています。
https://github.com/gx3n-inue/py_PlayBox
MIDI関連のWin32APIを利用し、python3で楽譜データを演奏させてみます。
音階とノートナンバーの変換表や、楽譜データのフォーマットは、PowerShell版と同じです。
#メインプログラム
#!python.exe
import ctypes
from ctypes import *
import os
import sys
from time import sleep
class myMIDI:
"""MIDI関連関数コール処理用クラス"""
def __init__(self, initData):
if sys.maxsize == 2 ** 63 - 1:
self.initData = c_int64(initData)
self.MIDI_MAPPER = c_int64(-1)
self.h = c_uint64(0)
else:
self.initData = c_int32(initData)
self.MIDI_MAPPER = c_int32(-1)
self.h = c_uint32(0)
def Init(self):
ctypes.windll.Winmm.midiOutOpen(byref(self.h), self.MIDI_MAPPER, 0, 0, 0)
ctypes.windll.winmm.midiOutShortMsg(self.h, self.initData)
def Out(self, outData, length):
ctypes.windll.winmm.midiOutShortMsg(self.h, outData)
sleep(length/1000.0)
def OutOnly(self, outData):
#print('%x = %x' %(id(self.h), self.h))
ctypes.windll.winmm.midiOutShortMsg(self.h, outData)
def Close(self):
ctypes.windll.winmm.midiOutReset(self.h)
class ScaleDefs:
"""音階定義を格納するクラス"""
def __init__(self, scale, note):
self.scale = scale
self.note = note
class PlayData:
"""楽譜データを格納するクラス"""
def __init__(self, scale, note, length):
self.scale = scale
self.note = note
self.length = length
def loadDefFile(filename):
"""音階定義ファイルを読み込む"""
# ファイルをオープンする
defFile = open(filename, "r")
# 行ごとにすべて読み込んでリストデータにする
lines = defFile.readlines()
# ファイルをクローズする
defFile.close()
defs = []
for temp in lines:
pos = temp.find("//")
# コメント開始文字"//"より前を取り出す
if pos >= 0:
temp = temp[:pos]
temp = temp.replace(" ", "").replace("\t", "").rstrip()
flds = temp.split("=")
if temp != "":
defs.append(ScaleDefs(flds[0],flds[1]))
return defs
def loadPlayFile(filename):
"""楽譜ファイルを読み込む"""
# ファイルをオープンする
playFile = open(filename, "r")
# 行ごとにすべて読み込んでリストデータにする
lines = playFile.readlines()
# ファイルをクローズする
playFile.close()
pData = []
for temp in lines:
pos = temp.find("//")
# コメント開始文字"//"より前を取り出す
if pos >= 0:
temp = temp[:pos]
temp = temp.replace(" ", "").replace("\t", "").rstrip()
flds = temp.split("=")
if temp != "":
pData.append(PlayData(flds[0], "", flds[1]))
return pData
def replaceScalt_to_Freq(defs, pData):
"""音階文字列を検索し、ノートナンバーをセットする"""
for currentData in pData:
scale = currentData.scale.split(",")
for temp in scale:
for currentLen in defs:
if temp == currentLen.scale:
if currentData.note == "":
currentData.note = currentLen.note
else:
currentData.note += "," + currentLen.note
break
def main():
argv = sys.argv
argc = len(argv)
if argc < 2:
#引数の個数チェック
print('Usage: python %s musicDataFile <timbre>' %argv[0] )
quit()
note_number_file = "note-number.dat"
if not os.path.exists(note_number_file):
print('%s not found.' %note_number_file)
quit()
if argc >= 3:
timbre = int(argv[2])
else:
timbre = 1
# 音階定義ファイルの読み込み
defs = loadDefFile(note_number_file)
# 楽譜ファイルの読み込み
pData = loadPlayFile(argv[1])
# ノート番号のセット
replaceScalt_to_Freq(defs, pData)
initData = timbre*256 + 0xc0
pm = myMIDI(initData)
pm.Init()
print("\nLoad Done. Play Start!!")
i = 0
for currentpData in pData:
if currentpData.note != "":
print('[%d] = %s( %s ), %s [ms]' %(i, currentpData.scale, currentpData.note, currentpData.length))
cnote = currentpData.note.split(",")
for data in cnote:
# 鍵盤を押す
play_on = "0x7f" + data + "90"
pm.OutOnly(int(play_on, 16))
sleep(int(currentpData.length) / 1000.0)
for data in cnote:
# 鍵盤を離す
play_off = "0x7f" + data + "80"
pm.OutOnly(int(play_off, 16))
else:
print('[%d] = rest, %s [ms]' %(i, currentpData.length))
# 休符
sleep(int(currentpData.length) / 1000.0)
i += 1
pm.Close()
print()
if __name__ == "__main__":
main()
#音階データとノート番号の変換表
楽譜データを入力する際に、ドレミ...もしくはdo,re,mi,...というように
音階で入力できるように、音階データとノート番号の変換表を作成しておきます。
// 全角カタカナ表記
ド4=3c
ド#4=3d
レ4=3e
レ#4=3f
ミ4=40
フ4=41
フ#4=42
ソ4=43
ソ#4=44
ラ4=45
ラ#4=46
...
(長いので途中省略)
...
...
re#8=6f
mi8=70
fa8=71
fa#8=72
so8=73
so#8=74
ra8=75
ra#8=76
si8=77
#楽譜データの作成
次に、演奏させたい曲の楽譜データを下記の書式で並べていきます。
音階およびオクターブ番号=演奏時間(ms)
『こぎつねこんこん』だとこんな感じになります。
ちなみに、同じ長さの音符であれば、和音も鳴らすことができます。
ド4,ド5 = 250
レ4 = 250
ミ4 = 250
フ4 = 250
ソ4,ソ5 = 500
ソ4,ソ5 = 500
ラ4 = 250
フ4 = 250
ド5 = 250
ラ4 = 250
ソ4,ソ5 = 1000
ラ4 = 250
フ4 = 250
ド5 = 250
ラ4 = 250
ソ4,ソ5 = 1000
ソ4 = 250
フ4 = 250
フ4 = 250
フ4 = 250
フ4 = 250
ミ4 = 250
ミ4 = 250
ミ4 = 250
ミ4 = 250
レ4 = 250
ミ4 = 250
レ4 = 250
ド4 = 250
ミ4 = 250
ソ4,ソ5 = 500
ソ4 = 250
フ4 = 250
フ4 = 250
フ4 = 250
フ4 = 250
ミ4 = 250
ミ4 = 250
ミ4 = 250
ミ4 = 250
レ4 = 250
レ4 = 250
ミ4 = 250
ド4,ド5 = 1000
#演奏
では早速、演奏してみましょう。
下記の書式でpy_PlayBox.pyを実行します。
D:\py_PlayBox> python py_PlayBox.py
Usage: python py_PlayBox.py musicDataFile <timbre>
timbreはMIDIの音色(楽器)の指定です。1~127の数値で指定してください。
以下は、さきほどの『ごぎつねこんこん』(PB_kitune.txt)をハーモニカ(23)で演奏する例です。
D:\py_PlayBox> python py_PlayBox.py PB_kitune.txt 23
Load Done. Play Start!!
[0] = ド4,ド5 ( 3c,48 ), 250 [ms]
[1] = レ4 ( 3e ), 250 [ms]
[2] = ミ4 ( 40 ), 250 [ms]
[3] = フ4 ( 41 ), 250 [ms]
[4] = ソ4,ソ5 ( 43,4f ), 500 [ms]
[5] = ソ4,ソ5 ( 43,4f ), 500 [ms]
[6] = ラ4 ( 45 ), 250 [ms]
[7] = フ4 ( 41 ), 250 [ms]
[8] = ド5 ( 48 ), 250 [ms]
[9] = ラ4 ( 45 ), 250 [ms]
[10] = ソ4,ソ5 ( 43,4f ), 1000 [ms]
[11] = ラ4 ( 45 ), 250 [ms]
[12] = フ4 ( 41 ), 250 [ms]
[13] = ド5 ( 48 ), 250 [ms]
[14] = ラ4 ( 45 ), 250 [ms]
[15] = ソ4,ソ5 ( 43,4f ), 1000 [ms]
[16] = ソ4 ( 43 ), 250 [ms]
[17] = フ4 ( 41 ), 250 [ms]
[18] = フ4 ( 41 ), 250 [ms]
[19] = フ4 ( 41 ), 250 [ms]
[20] = フ4 ( 41 ), 250 [ms]
[21] = ミ4 ( 40 ), 250 [ms]
[22] = ミ4 ( 40 ), 250 [ms]
[23] = ミ4 ( 40 ), 250 [ms]
[24] = ミ4 ( 40 ), 250 [ms]
[25] = レ4 ( 3e ), 250 [ms]
[26] = ミ4 ( 40 ), 250 [ms]
[27] = レ4 ( 3e ), 250 [ms]
[28] = ド4 ( 3c ), 250 [ms]
[29] = ミ4 ( 40 ), 250 [ms]
[30] = ソ4,ソ5 ( 43,4f ), 500 [ms]
[31] = ソ4 ( 43 ), 250 [ms]
[32] = フ4 ( 41 ), 250 [ms]
[33] = フ4 ( 41 ), 250 [ms]
[34] = フ4 ( 41 ), 250 [ms]
[35] = フ4 ( 41 ), 250 [ms]
[36] = ミ4 ( 40 ), 250 [ms]
[37] = ミ4 ( 40 ), 250 [ms]
[38] = ミ4 ( 40 ), 250 [ms]
[39] = ミ4 ( 40 ), 250 [ms]
[40] = レ4 ( 3e ), 250 [ms]
[41] = レ4 ( 3e ), 250 [ms]
[42] = ミ4 ( 40 ), 250 [ms]
[43] = ド4,ド5 ( 3c,48 ), 1000 [ms]
D:\py_PlayBox>