Pyxel アプリで Oggファイルを再生する（ローカルPC、Web、plumOS-RN）

:::note warn
注意
バージョンや環境によってはうまく動かない可能性があります
投稿時の各バージョンは以下です
* pyxel：2.2.7
* pygame-ce：2.5.2
* [plumOS-RN](https://github.com/game-de-it/plumOS-RN)：v2.0
:::


# はじめに
Pyxelは1.0がリリースされた時にちょっと触ったぐらいの経験だったのですが、、、
以下の2つの投稿を見て、久々に Pyxel 熱が再燃してきました🏃

https://qiita.com/kitao/items/eae53dd47c663b497352

https://qiita.com/shiromofufactory/items/7f5c44b55343bf90651b


最近は、以下のようなテキストアドベンチャーエンジンもどきを、少しずつ作っています👀
Pyxelの思想を考えると、少々邪道な作りですが、、、
※イメージバンク乱用していたり、BGMはpygame使ってフリー素材のOggファイルに頼り切りだったり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/cf1300d8-7779-5754-6f19-1e8ffd4caf12.png)
PCで動かせるのはもちろん、Webで実行出来たり、中華ゲーム機（plumOS利用）で実行出来たり、色々なプラットフォームで動かせるのは楽しいです👾

ただ、上述の通り、BGM再生をOggに頼り切ってしまうと、
WebやplumOSで実行する際に一手間が必要になってきます。

この投稿では、WebやplumOS-RNでOggを再生する方法の一例を記載しています。

この投稿のソースコードは以下にコミットしています。

https://github.com/Kazuhito00/Qiita-AdventCalendar-20241214-Pyxel

# 1．ローカルPC
まずは、ローカルPCでのOggファイル再生です。

https://x.com/KzhtTkhs/status/1867544749051363591

ソースコードは以下です。
pygame-ceを使用しているだけなので、あまり特別なことはしていませんね。
```python:sample.py
import pyxel
import pygame


class Sample:
    def __init__(self):
        self.play_flag = False

        # Oggファイルパス
        self.ogg_path = "maou_bgm_acoustic22.ogg"

        # pygame初期化
        pygame.mixer.init()

        # Pyxel初期化
        pyxel.init(256, 256)
        pyxel.run(self.update, self.draw)

    def update(self):
        if not self.play_flag:
            # Oggファイル読み込み
            pygame.mixer.music.load(self.ogg_path)

            # Oggファイル再生
            pygame.mixer.music.play(-1)  # -1はループ再生

            self.play_flag = True

    def draw(self):
        pyxel.cls(7)

        # 再生表示
        if self.play_flag:
            pyxel.text(2, 2, "Now Playing...", 1)
            pyxel.text(2, 10, self.ogg_path, 1)


Sample()
```

実行する時は以下の通り。

```python:sample.py
cd 01.Local-PC
python sample.py
```

# 2．Web
次にWeb実行です。
せっかくのWeb実行なので、GitHub Pagesに公開しています。

https://kazuhito00.github.io/Qiita-AdventCalendar-20241214-Pyxel/02.Web/

Web実行用のソースコードは以下です。
Pythonコード自体はローカルPCと同じソースコードですね。
```python
import pyxel
import pygame


class Sample:
    def __init__(self):
        self.play_flag = False

        # Oggファイルパス
        self.ogg_path = "maou_bgm_acoustic22.ogg"

        # pygame初期化
        pygame.mixer.init()

        # Pyxel初期化
        pyxel.init(256, 256)
        pyxel.run(self.update, self.draw)

    def update(self):
        if not self.play_flag:
            # Oggファイル読み込み
            pygame.mixer.music.load(self.ogg_path)

            # Oggファイル再生
            pygame.mixer.music.play(-1)  # -1はループ再生

            self.play_flag = True

    def draw(self):
        pyxel.cls(7)

        # 再生表示
        if self.play_flag:
            pyxel.text(2, 2, "Now Playing...", 1)
            pyxel.text(2, 10, self.ogg_path, 1)


Sample()
```
htmlファイルには少し対応が必要です。

PyxelのWeb実行では、追加で利用できるパッケージが限られています（PyxelはPyodideを利用しているため、Pyxelの制約と言うよりはPyodideの制約）
利用できるパッケージは、[Pyodide 対応パッケージ](https://pyodide.org/en/stable/usage/packages-in-pyodide.html)に記載されている通りです。今回は pygame-ce を利用しています。

追加パッケージを利用する場合には、\<pyxel-play>タグの packages にパッケージ名を追加します。
```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <script src="https://cdn.jsdelivr.net/gh/kitao/pyxel/wasm/pyxel.js"></script>
    </head>

    <body>
        <pyxel-play root="." name="02.Web.pyxapp" packages="pygame-ce"></pyxel-play>
    </body>
</html>
```

実行する時は以下の通り。
```python:sample.py
cd 02.Web
pyxel package ./ sample.py
python -m http.server 8000  # http://localhost:8000
```

# 3．plumOS-RN
最後にplumOS-RNでの実行です。
少々、録画の音量が小さいですが、以下のように動作しています。

https://x.com/KzhtTkhs/status/1867544519975301380

~~ローカルPCやWebでは、pygame-ce が利用できたので、pygame-ce のBGM再生機能をそのまま使用しました。
plumOS-RNでは、任意のパッケージは（現時点では）追加できないので、もう少し別の対応をしています。~~

【2024年12月14日追記】
ゲームで見るITの世界さんから情報いただきました。
portsやSSH接続でpipインストール出来るとのこと（試していませんがPythonスクリプトからのsubprocessでもいける？）
これで、sample.pyはどのプラットフォームでも共通で行けますね。ありがとうございます。

https://x.com/0_game_it/status/1867939214765260849

以下は、訂正前の情報です。作業ログとして残しておきます👀

pygame-ce は内部では、SDL2と言うマルチメディアライブラリを利用して、マルチメディアを扱っています。

plumOS-RNにも、SDL2自体はインストールされているため、
Pythonから直接SDL2を使用します（/usr/lib/の下にsoファイルがいます）

```python:sample.py
import ctypes
from ctypes import c_int, c_char_p

import pyxel


class Sample:
    def __init__(self):
        self.play_flag = False

        # Oggファイルパス
        self.ogg_path = "maou_bgm_acoustic22.ogg"

        # Pyxel初期化
        pyxel.init(256, 256)
        pyxel.run(self.update, self.draw)

    def update(self):
        if not self.play_flag:
            # SDL2およびSDL2_mixerの共有ライブラリをロード
            sdl2 = ctypes.CDLL("/usr/lib/libSDL2.so")
            sdl2_mixer = ctypes.CDLL("/usr/lib/libSDL2_mixer.so")

            # SDL2およびSDL2_mixerの関数を定義
            sdl2.SDL_Init.argtypes = [c_int]
            sdl2.SDL_Init.restype = c_int
            sdl2_mixer.Mix_Init.argtypes = [c_int]
            sdl2_mixer.Mix_Init.restype = c_int
            sdl2_mixer.Mix_OpenAudio.argtypes = [c_int, c_int, c_int, c_int]
            sdl2_mixer.Mix_OpenAudio.restype = c_int
            sdl2_mixer.Mix_LoadMUS.argtypes = [c_char_p]
            sdl2_mixer.Mix_LoadMUS.restype = ctypes.c_void_p
            sdl2_mixer.Mix_PlayMusic.argtypes = [ctypes.c_void_p, c_int]
            sdl2_mixer.Mix_PlayMusic.restype = c_int

            # SDL2を初期化
            sdl2.SDL_Init(0x00000010)  # オーディオサブシステムを有効化

            # SDL_mixerのOGGサポートを有効化
            sdl2_mixer.Mix_Init(0x00000002)  # Oggの初期化フラグ

            # オーディオデバイスを初期化
            # 周波数: 44100Hz, フォーマット: 16ビット, チャンネル: ステレオ, チャンクサイズ: 1024
            sdl2_mixer.Mix_OpenAudio(44100, 0x8010, 2, 1024)

            # Oggファイル読み込み
            music = sdl2_mixer.Mix_LoadMUS(self.ogg_path.encode("utf-8"))

            # Oggファイル再生
            sdl2_mixer.Mix_PlayMusic(music, -1)  # -1はループ再生

            self.play_flag = True

        # AボタンかBボタンで終了
        if pyxel.btnp(pyxel.GAMEPAD1_BUTTON_A) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_B):
            pyxel.quit()

    def draw(self):
        pyxel.cls(7)

        # 再生表示
        if self.play_flag:
            pyxel.text(2, 2, "Now Playing...", 1)
            pyxel.text(2, 10, self.ogg_path, 1)


Sample()
```



実行する時は以下の通り。
出来上がった「03.plumOS-RN.pyxapp」をWinSCPなどで、plumOS-RNを導入した中華ゲーム機へコピーしてください。
```python:sample.py
cd 03.plumOS-RN
pyxel package ./ sample.py
```


# 使用BGMについて
BGMは、[魔王魂](https://maou.audio/#google_vignette) 様の [アコースティック22](https://maou.audio/bgm_acoustic22/) を使用しております。
魔王塊様はゲーム制作の強い味方です。ありがとうございます🙇

---

以上、Oggファイルを再生したお話でした。