osad-sakana
← Works

Pyxel互換音楽エディタ

pyxres-piano-roll

Pyxel互換音楽エディタ

GitHub ↗アプリ ↗Web
Pyxel互換音楽エディタのスクリーンショット

Pyxel互換音楽エディタ

ブラウザ単体で動く、Pyxel互換の音楽エディタです。 ピアノロールで打ち込んだ曲を .pyxres(format_version = 1)として書き出し、 Pyxel実機の pyxel.load() + pyxel.playm() でそのまま再生できます。

非公式ツールです: 本プロジェクトはファンメイドのサードパーティ製ツールであり、 Pyxel開発者の承認を受けたものではありません。Pyxelはkitao氏の 著作物です。本エディタで生成したファイルに起因する不具合をPyxel本体へ報告しないでください。

使い方

ビルド不要・外部依存ゼロです。いずれかで開きます。

# そのまま開く
open index.html

# または静的サーバ経由
python3 -m http.server 8930
# → http://localhost:8930

pyxresは書き出し専用です。編集の正本は内部JSONで、旧フォーマット(v1)のJSONは読み込み時に自動変換されます。

実機での確認(pyxel-test)

書き出したpyxresをPyxel実機エンジンで再生するCLIを同梱しています。

uv run pyxel-test <pyxresファイル>
キー 動作
0〜7 音楽スロットを選択して再生
← → データのあるスロット間を移動
SPACE 再生 / 停止
Q / ESC 終了

Pyxelアプリへの組み込み方

書き出した .pyxres はそのまま pyxel.load() で読み込めます。画像・タイルマップは 空(images = [] / tilemaps = [])なので、Pyxel Editorなどで作った画像用の .pyxres と分けて管理し、exclude_* 引数で合成するのが基本形です。

import pyxel

pyxel.init(160, 120)

# 画像・タイルマップは別ファイルから読み込む(音楽データには触れない)
pyxel.load("graphics.pyxres", exclude_sounds=True, exclude_musics=True)

# 音楽データはこのエディタが書き出したファイルから読み込む(画像は空なので触れない)
pyxel.load("music.pyxres", exclude_images=True, exclude_tilemaps=True)

def update():
    if pyxel.btnp(pyxel.KEY_SPACE):
        pyxel.playm(0, loop=True)  # 書き出しダイアログでスロット0に割り当てた曲を再生

def draw():
    pyxel.cls(0)

pyxel.run(update, draw)

データモデル

内部フォーマットv6。曲(Song)がパターンを内包します。

Project
 └─ songs: Song[]                  … 曲数は無制限(書き出し時に8スロットへ選択割り当て)
     ├─ bpm                        … 20〜900。書き出し時 speed = round(1800 / bpm)(1列=16分音符)
     ├─ transpose                  … ±59半音。再生・書き出し時に全ノートへ非破壊で適用
     ├─ timeSignature              … "4/4" | "3/4"。曲全体で1つ。1小節の列数を決める(4/4=16列・3/4=12列)
     ├─ patterns: Pattern[]        … 1曲あたり最大64個
     │   ├─ rateMode               … normal / double(2倍) / half(1/2倍)。speedを1/2倍・2倍に変換
     │   └─ lengths                … 音価(ノートが占有する列数)。書き出し時に同音程の連続ノートへ分割
     └─ channels: (patternId|null)[][] … 最大4チャンネルのグリッド。null=1小節(timeSignature基準の列数)の休符セル。
                                      書き出し時は曲ごとに1つの休符サウンドへ変換(64枠を1つ消費)

ピアノロール上部には、パターン内ローカルの小節番号(1始まり)をルーラー表示します。 パターンは曲内で複数回・任意位置に配置されうるため、曲全体を通した小節番号はエディタの外に出ません。

拍子を変えてもパターンの列数(長さ)自体は変わらず、空白セル(休符)の長さのみ追従します。 そのため4/4で組んだ曲を後から3/4へ切り替えると、既存パターンの小節境界がずれて見えることがあります (データは非破壊で、拍子を戻せば表示も戻ります)。3/4を使う場合は曲の最初から3/4で組むのがおすすめです。

Pyxel由来の制約(音域60音階 C0〜B4・4波形・音量0〜7・エフェクト6種)はUIで構造的に超えられないようにしており、 事後エラーは「書き出し対象の参照パターン合計が64を超えた場合」のみです。

アーキテクチャ

依存方向は一方向(UI → Model、AudioEngine/Exporter → Model)。ModelはDOM・Web Audioに依存しない純粋データ層です。

モジュール 役割
js/model.js プロジェクトデータ・バリデーション・書き出し割り当て・v1マイグレーション
js/audio.js Pyxel準拠の22,050Hzオフラインレンダラ(純関数)とWeb Audio再生
js/exporter.js TOML生成+無圧縮ZIP梱包(CRC-32含む自前実装)+標準MIDI生成
js/storage.js localStorage自動保存・JSONファイル入出力
js/ui/ ピアノロール・チャンネルレーン・リスト・プロパティ・ダイアログ
js/app.js 状態管理(イミュータブル更新)とビューの束ね

音声エンジンはpyxel-coreの実装に準拠しています(4波形・NES APU方式LFSRノイズ・ Slide/Vibrato/FadeOut/Half/Quarterエフェクト・チャンネルゲイン0.125)。詳細は設計書§5を参照。

開発

# ユニットテスト(Model / Exporter / AudioEngine / Storage)
node --test test/*.test.js

# Pyxel実機での読み込み検証(テスト用pyxresを生成して検証)
node tools/make_test_pyxres.js /tmp/verify.pyxres
uv run python tools/verify_with_pyxel.py /tmp/verify.pyxres

実機検証はPyxel 2.9.7で、エディタ生成の2曲入りpyxres(曲内パターン共有・再生モードあり)が pyxel.load()で読み込め、sounds/musicsの内容が一致し、playm()が通ることを確認しています。

ライセンスとクレジット

本プロジェクトは MIT License です。