20
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

nana musicAdvent Calendar 2018

Day 16

レトロゲームエンジンPyxelで作曲してみる

Last updated at Posted at 2018-12-15

nana music マーケティングチームの @usangit です。
最近話題になった Pyxel を使って遊んでみようと思います。

Pyxel とは?

Python 向けのレトロゲームエンジンです。

02_jump_game.gif
https://github.com/kitao/pyxel

ファミコン世代には懐かしいあのレトロゲームを Python で作れるということで、本記事では Pyxel の作曲機能に注目してみます。

付属の Pyxel Editor を使用することで GUI を使用して画像やサウンドを作成することが出来ますが、今回は Python コードのみでレッツチャレンジ!

まずは出来上がったサウンドを聴いてみる

懐かしのファミコン曲を Pyxel で打ち込んでみました。1

https://nana-music.com/sounds/046ddc00/
<a href="https://nana-music.com/sounds/046ddc00/"

コードはこんな感じ

サウンドの一部のみ抜粋、引用2


import pyxel


class App:
    def __init__(self):
        pyxel.init(200, 150, caption="Sound API")

        # ch0 前半
        pyxel.sound(0).set(
            "c2re2rg2rc3c3 c3c3b2a2g2f2e2d2",
            "p",
            "6",
            "n",
            20,
        )

        # ch0 後半
        pyxel.sound(1).set(
            "c2re-2rg2rc3c3 c3c3b2c3d3c3b2d3",
            "p",
            "6",
            "nnnnnnnn nnnnnnnn",
            20,
        )

        # ch1 前半
        pyxel.sound(2).set(
            "c1c1rrrrrr g1g1g1g1g0g0g0g0",
            "t",
            "6",
            "n",
            20,
        )

        # ch1 後半
        pyxel.sound(3).set(
            "rrrrrrrrrrrrrrrr",
            "t",
            "6",
            "n",
            20,
        )

        # ch2 前半
        pyxel.sound(4).set(
            "e1e1rrrrrr rrd2c2b1a1g1b1",
            "s",
            "3",
            "n",
            20
        )

        # ch2 後半
        pyxel.sound(5).set(
            "rrrrrrrrrrrrrrrr",
            "p",
            "5",
            "nnnnnnnn nnnnnnnn",
            20
        )

        self.is_playing = [True] * 3
        self.play_music(True, True, True)
        pyxel.run(self.update, self.draw)

    def play_music(self, ch0, ch1, ch2):
        self.is_playing = (ch0, ch1, ch2)

        if ch0:
            pyxel.play(0, [0, 1], loop=True)
        else:
            pyxel.stop(0)

        if ch1:
            pyxel.play(1, [2, 3], loop=True)
        else:
            pyxel.stop(1)

        if ch2:
            pyxel.play(2, [4, 5], loop=True)
        else:
            pyxel.stop(2)

    def update(self):
        if pyxel.btnp(pyxel.KEY_Q):
            pyxel.quit()

        if pyxel.btnp(pyxel.KEY_SPACE):
            if True in self.is_playing:
                self.play_music(False, False, False)
            else:
                self.play_music(True, True, True)

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

App()
  • 作曲にはオーディオ API のサウンドクラスを使用
  • Pyxel で使用できるチャンネルは ch0 〜 ch3 の計4チャンネルでチャンネルそれぞれに対して各値を設定することが可能
  • 各チャンネルごとに再生/停止することが可能(今回は使用していません)
  • キーイベントとして Qキー で終了、SPACEキー で再生/停止を定義

今回のメイン、音符の打ち込みはset(note, tone, volume, effect, speed) を使用します。

speed

1音の長さ (120 = 1音1秒)
今回は 1ノート = 16分音符 になるように 20 を設定

note

'CDEFGAB'+'#-'+'0123'または'R'の文字列で音程を設定する。大文字と小文字を区別せず、空白は無視される

例えば、C216 分音符の ド の音。

G2G2G2G2 のように連続で打ち込むと 4 分音符の ソ の音になり、
スタッカートで打ち込みたい場合は G2RG2R と間に休符の R を入れます。

tone

音色 (0:Triangle / 1:Square / 2:Pulse / 3:Noise) のリスト
頭文字を設定。p は Pulse

音色は以下パートで使われることが多いハズ

音色 主なパート
Triangle(三角波) ベース音
Square(矩形波) 主旋律、副旋律
Pulse(パルス波) 主旋律、副旋律
Noise(ノイズ) ドラム、パーカッション

effect

エフェクト (0:None / 1:Slide / 2:Vibrato / 3:FadeOut) のリスト

'NSVF'の文字列でエフェクトを設定
1文字が1音の長さに対応

サウンド では以下のポイントでエフェクトを入れてみました

  • 0:36 Vivrato
  • 0:48 FadeOut

まとめ

仕様もシンプルで用意されているクラスを使うことですぐにゲームが作れるので、Python を使ったことがあり、MIDI や DTM に馴染みのある人ならすんなり始めることが出来そうでした。
いつか Pyxel を使ったゲーム公開できればと思います。

nana music Advent Calendar 2018 はまだまだ続きます!
明日は @yamanemur さんです。どうぞお見逃しなく💁

番外編

試しにイメージクラスを追加して画面描画してみました。

import math
import pyxel


class App:
    def __init__(self):
        pyxel.init(200, 150, caption="Image API")

        pyxel.image(0).set(
            0,
            0,
            [
                "11011100000011101100",
                "11111110000111111100",
                "11100111001110011100",
                "11000011001100001100",
                "11000011001100001100",
                "11000011001110011100",
                "11000011000111111100",
                "11000011000011110100",
            ],
        )

    def draw(self):
        pyxel.cls(0)
        pyxel.text(58, 60, "nana music", 7)
        pyxel.text(58, 70, "Advent Calendar 2018", 7)
        pyxel.rectb(6, 97, 193, 143, 7)
        pyxel.text(12, 102, "Everyone is a Co-Creator", 7)
        pyxel.text(12, 110, "", 7)

        for i, v in enumerate([0, 1]):
            pyxel.pal(1, 14)
            pyxel.blt(
                156 + i * 21,
                7 + math.sin(pyxel.frame_count * 0.2 + i * 1.1) * 2,
                0,
                0,
                0,
                20,
                8,
                0,
            )
        pyxel.pal()

App()

  • pyxel.imageでイメージのデータ (NumPy配列)をセット(よーくみると 01 のビットマップで na の文字が見えますね)
  • draw 関数で画面描画
  • for文内で 正弦*フレームカウント でイメージに動きを付ける

実行してみると、、、

それっぽい!

  1. C4以上の高さになるとピッチがズレるため、ところどころ1オクターブ下げている

  2. 大人の事情

20
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?