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
- 曲・パターン: 左サイドバーで追加・選択・リネーム・削除。パターンは曲ごとに管理されます
- ピアノロール: クリックで配置/削除、ドラッグで移動。
←→で列移動、↑↓でノートを半音移調 (範囲選択中は選択範囲に重なる全ノートが対象。範囲外へはみ出した部分も一緒に動きます。 範囲に重なる音が1つでも音域端を超える場合は何も動きません)、Enterで音符⇔休符 - 選択・コピー・ペースト:
Shift+←→/Shift+クリック/Shift+ドラッグで範囲選択、 小節番号ルーラーのクリック/ドラッグで列単位の範囲選択、Ctrl(Cmd)+Aで全選択、Escで解除。Ctrl(Cmd)+C/X/Vでコピー・カット・ペースト(アプリ内のみ、OSクリップボードとは連携しません)。 貼り付け先の既存ノートは上書きされ、パターン末尾を超える分は切り詰められます。 選択範囲の前後にはみ出すノートは、範囲外に残る部分を保ったまま範囲内だけが削除されます (ノートが2つに分かれることがありますが、選択していない列の音は失われません)。 そのためカット→同じ位置へ貼り直せば鳴る音は元と変わりません。 キャレット未選択の状態で貼り付けるとパターン先頭に貼り付けられます。 tone/volume/effectはコピー対象外です(ノート移動と同じ扱いで、値は列位置に紐づいたまま動きません) - 音価: ノート右端をドラッグで長さを変更(Cubase風)。pyxresに音価の概念はないため、書き出し時に同音程の連続ノートへ分割されます
- 曲構造: グリッドのセルへパターンをドラッグ&ドロップ(またはクリック)して配置します(4チャンネルまで)。空白セル(1小節の休符)を挟めるため、トラックの途中からでも開始できます。ブロックの✕は空白化(タイミング維持)、空白セルの✕で詰められます
- BPM: 曲構造ヘッダで曲ごとに設定(20〜900)
- 移調: 曲構造ヘッダで曲ごとに±59半音。再生・書き出し時に全ノートへ非破壊で適用され、音域外は端へクランプ(書き出し時に警告表示)
- 再生モード: パターンごとに 通常/2倍/1/2倍 を選択
- 保存: 編集は1秒デバウンスでlocalStorageへ自動保存。可搬性が必要なら「JSON保存/読み込み」
- 書き出し: 「pyxres書き出し」で最大8曲を音楽スロットへ割り当ててダウンロード
- MIDI書き出し: 「MIDI書き出し」で現在の曲を標準MIDI(Type 1、チャンネル=トラック)で出力。音価は1ノートのまま保持し、テンポはPyxelの実speed基準。全音色ピアノ扱い(ノイズはPyxelと同じく+60音域)
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)
pyxel.load()は除外した種別のリソースには触れないため、2回呼んでも先に読み込んだ 内容を上書きしません(Pyxel 2.9.7で動作確認済み。詳細はtools/verify_with_pyxel.py)。- どの曲がどのスロット(0〜7)に入っているかは、書き出しダイアログでのスロット割り当てで決まります。
- 曲の切り替えやループは
pyxel.playm(slot, loop=True)を呼び直すだけです。 - 1ファイルに画像と音楽を両方持たせたい場合は、
pyxel.save()のexclude_*は使わず そのまま統合済みファイルとして保存し直しても構いません(Pyxelの.pyxresは元々 4セクションを1ファイルに持つ形式です)。
データモデル
内部フォーマット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 です。
- Pyxel(MIT License, © kitao)— 本エディタが互換対象とするレトロゲームエンジン
- 再生エンジンの各パラメータ(サンプルレート・波形定義・ゲイン・LFSRシード・エフェクト仕様など)は、
pyxel-core(MIT License)のソースコード(
settings.rs/voice.rs/sound.rs/channel.rs)を 参照して導出したものです。コードの複製・翻訳は含みません .pyxresフォーマットは公式仕様 pyxres-format.md に準拠しています
