見出し画像

[Pyxel] 利用できるサウンドを増やす

以前書いた記事でも触れた話なんですが

Pyxelがデフォルトで提供するクラスリスト(tonesとかimagesとか)はサイズ可変です。それぞれのリストの中身オブジェクトを並べたリストを作って被せるだけ。やり方は前述の記事を見て貰えば分かると思います。

そしてその中身として登録されるオブジェクト(pyxel.Image、pyxel.Sound等)は、リストとは別の独立インスタンスが作れます。

今回はそのサウンド版、加えてpyxresのデータを独立インスタンスに設定しやすくする為のスクリプトの話です。
効果音一杯入れたり4トラックの曲をいくつも作るとサウンド定義の64は割とすぐ使い切ってしまいますもんね。

サムネがもう答えなんですが、

# otameshi.py
var = pyxel.Sound()
var.set(~~~~) # パラメタ群については後述

pyxel.play(channel_no, var)
#あるいは
pyxel.channels[3].play(var)

とかやれば、好きなようにサウンドオブジェクトを作って音を鳴らせます。
例によって公式リファレンスから引用すると・・・

sndの設定値には「Soundインスタンス」が定義できる

bltコマンドにイメージリストの番号だけでなくImageインスタンスを定義出来るのと同様ですね。

さて、Soundを鳴らすにはインスタンスを作ればいいという話ではなく、Imageだったら画像を読み込む必要があるように、Soundには音程や音色の設定が必要です。
定義の内容はこちら。

これが何かと言われたら・・・

サウンドエディタの項目を見てもらうと分かります。
 音程はCから始まるドレミファドン
 音色はT(riangle),S(quare),P(ulse),N(oise)の頭文字
 音量は0~7の数字
 エフェクトは一杯あるので引用で・・
 速度は1~99の数値
となってます。

このnoteを書いてる間にHQの存在を知りました…見落としてた


この値達をコードにペタっと貼って書いておけば、Soundインスタンスの定義は完成です!

ですが!

コードでこれを書いて「どんな音が鳴るか?」って想像出来ませんよね?
出来る人が居たら教えて下さい。色々教えて下さい。


(そもそもこんな風にSoundを作る人自体ほぼ居ないかも知れませんが)
Soundの設定自体はPyxelEditorを使うのがいいに決まっています。
なんてったって公式ツールですから。
ただ、PyxelEditorからこのコードに貼れる形にデータやテキストを出力できる機能はありません。分かる方ならpyxresの中身を覗いたりできるでしょうけど、Sound.setコマンドのパラメタとは記法が違うのでこれもそのままは使えません。

「なんとかしてよクロえも~~~ん」と泣きつきました

平素よりお世話になっております

で、何度かやり取りした結果出来上がったのがこちらです。
※実行して何かあっても筆者は責任は負いませんのであしからず

import pyxel

NOTE_NAMES = ["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"]

def note_to_name(n):
    if n < 0:
        return "r"
    return f"{NOTE_NAMES[n % 12]}{n // 12}"

def tone_to_char(t):
    return "tspn"[t] if t < 4 else str(t)

def effect_to_char(e):
    return "nsvfhq"[e] if e < 6 else "n"

pyxel.init(0, 0, headless=True)
pyxel.load("assets.pyxres")

for i, snd in enumerate(pyxel.sounds):
    notes = "".join(note_to_name(n) for n in snd.notes)
    tones = "".join(tone_to_char(t) for t in snd.tones)
    volumes = "".join(str(v) for v in snd.volumes)
    effects = "".join(effect_to_char(e) for e in snd.effects)
    print(f'{i}: "{notes}", "{tones}", "{volumes}", "{effects}", {snd.speed}')

実行するとこんな感じで出力されます。

全サウンド定義を出力

実行方法は各自お好きな方法でどうぞ。
Pyxelアプリに出力するよりコンソール出力してコピペが楽だと思ったんですが、スクリプトを単体で実行すると結果も一瞬で消えてしまうのでご注意ください。


なんでこんな事を始めたのかというと、これも以前の記事なんですが自分がゲーム作る為のライブラリとしてメニュー機能を作ってまして。

カーソル移動時や決定ボタン時のSEを、ゲーム本体でなくライブラリで個別に持っておきたいなあという要求からでした。
メニューカーソル用に割り当てた番号に別のSEを作ってしまって、サウンドの番号振り直しというか別番号への移設が非常に手間だったもので。。。


2026/7/26追記
読み返しててふっと気付いたんですが…
記事の上の方のotameshi.py、どうせなら

se_ch = pyxel.Channnel()
se_ch.play(var. resume=True)

とかにすれば、効果音でBGMのチャンネルつぶさずに済む…


かなと思って試してみましたが、音が鳴りませんでした。
私はrustが読めないのでClaudeさんに解析して貰った所、

AIの回答
は、誤っている場合があります

という事らしいです。
つまり、独立したChannnelオブジェクトを作ってあれこれ設定しても、音を鳴らす処理の流れに乗りませんって事みたいです。
そこに繋がる流れはpyxel.channelsだけ。

        channels: list[px.Channel] = []
        for _ in range(ch_max):
            channel = px.Channel()
            channel.gain = 0.95/ch_max
            channel.detune = 0
            channels.append(channel) 
        px.channels[0:ch_max] = channels

などとしてチャンネル数を拡張すれば、ちゃんとpyxel.channelsに乗ってる分はちゃんと鳴ります。