🐹

【Pyxel】SlackBot に MML → .wav 変換を仮実装してみた

に公開

2025/10/21 Pyxel 2.5.8 時点の内容です

きっかけから実験へ

https://x.com/sharlm/status/1980299513178054998

某 Slack で楽譜を貼り付けて「この曲を吹けるようになりたい」という某 X 氏に刺激を受けて『MMLをSlackに投げたら音声ファイルにして貼り付ける』方法を模索していたら #pyxel にたどり着き

Python で MML から簡単に .wav を出力できるものを探す

いろいろ検索していたら『Python 向けのレトロゲームエンジン』というものが!!!
https://github.com/kitao/pyxel

ピコピコゲーを比較的簡単に作成できる、というのは聞いていたが 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