🐹
【Pyxel】SlackBot に MML → .wav 変換を仮実装してみた
2025/10/21 Pyxel 2.5.8 時点の内容です
きっかけから実験へ
某 Slack で楽譜を貼り付けて「この曲を吹けるようになりたい」という某 X 氏に刺激を受けて『MMLをSlackに投げたら音声ファイルにして貼り付ける』方法を模索していたら #pyxel にたどり着き
Python で MML から簡単に .wav を出力できるものを探す
いろいろ検索していたら『Python 向けのレトロゲームエンジン』というものが!!!
ピコピコゲーを比較的簡単に作成できる、というのは聞いていたが 2.3.0 で mml メソッドが実装されてたみたい
素晴らしい!!
SlackBot 用の雛形を作る
雛形
# -*- coding: utf-8 -*-
from tempfile import mkstemp
import html
import os
import pyxel
pyxel.init(0, 0)
def parse(mml):
tracks = {}
MML_PREFIX = 'MML#'
for _l in mml.strip().splitlines():
_track = line = ''
_l = _l.strip()
try:
if _l.startswith(MML_PREFIX):
_track, line = _l.split(' ', maxsplit=1)
else:
_track = MML_PREFIX + '0'
line = _l
except ValueError:
pass
# unescape
line = html.unescape(line)
# parse
if _track.upper().startswith(MML_PREFIX):
track = _track.upper().removeprefix(MML_PREFIX)
if track.isnumeric():
track = int(track)
else:
track = 0
else:
track = 0
if track not in tracks:
tracks[track] = []
tracks[track].append(line)
# reset
for track in range(4):
pyxel.sounds[track].mml()
# entry sounds
for track in tracks:
pyxel.sounds[track].mml(' '.join(tracks[track]))
# mixup
pyxel.musics[0].set([0], [1], [2], [3])
sec = pyxel.sounds[0].total_sec()
# get temporary filename
_, outfile = mkstemp()
os.unlink(outfile)
# save
pyxel.musics[0].save(outfile, sec)
return f'{outfile}.wav'
if __name__ == '__main__':
mml = """
MML#0 T150 L8 o4
aaf+ear>c4 <aaf+earf+4
aaf+ea>cdd+ eL16d+ed+ed+ee4edc+<b
MML#1 T150 L8 o2 [aa>aa]6 <ee>eee<ef+g+
"""
outfile = parse(mml)
print(__name__, outfile)
MML は某 X 氏に書いてもらいました!!
改善されるといいな
コードを書いていて「Python の例外が発生しない」ことに気がつきました
$ python -c 'import pyxel as x; x.init(0,0); x.sounds[0].mml("cdefg"); print("=" * 10)'
==========
$ python -c 'import pyxel as x; x.init(0,0); x.sounds[0].mml("cdefgh"); print("=" * 10)'
thread '<unnamed>' panicked at pyxel-engine/src/mml_parser.rs:44:9:
MML:5: Unexpected character 'h'
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Traceback (most recent call last):
File "<string>", line 1, in <module>
pyo3_runtime.PanicException: MML:5: Unexpected character 'h'
MML パーサは Rust で書かれていたので python bindings のところでパーサのエラー部分もエラーハンドラに追加してあげればよさそうだな、とは思いましたがそこまでは手がつけられず……
Rust カイタコトナイ
最後に
ゲームエンジンの美味しいところだけをいただいて SlackBot に実装してみました
かなり簡単にできたので、開発している方に感謝します
みなさんも Enjoy!!
Discussion