pyxen.Music

Entityにコンポーネントとしてアタッチされるストリーミングオーディオトラックです。

Musicはオンデマンドでデコードされるため、BGMやアンビエンスなどの長いトラックに適しています。

オーディオのコンセプトについてはオーディオを参照してください。


コンストラクタ

Music(filename, *, volume=1.0, pan=0.0, loop=False)

filename は唯一の位置引数で、プロジェクト内の音楽アセットの名前です(拡張子なし)。残りの3つはキーワード専用で、同名のプロパティの初期値を設定します(コンポーネントのプロパティを参照)。


パラメータ

パラメータ型説明
filenamestr音楽アセットの名前(拡張子なし)
volumefloat初期ゲイン。デフォルトは 1.0
panfloat初期のステレオ位置(-1.0〜1.0)。デフォルトは 0.0
loopboolトラックの終了時に最初から再生する。デフォルトは False

例

theme = Music("theme", volume=0.6, loop=True)

Musicのアタッチ

生成時:

bgm = world.spawn(music=Music("theme"))

または後から:

entity.music = Music("theme")

ミュージックを割り当てると即座に再生が開始されます。


コンポーネントのプロパティ

entity.musicでアクセス:

プロパティ型R/W説明
loopboolR/Wループを有効にする
volumefloatR/Wゲイン(0.0以上にクランプ)
panfloatR/Wステレオパンニング(-1.0〜1.0)
timefloatR現在の再生位置(秒)
durationfloatR全体の長さ(秒)
finishedboolR再生が完了するとTrue
channelsリスト風Rトラックの各パート。それぞれが独自の volume を持つ — 「チャンネル」の節を参照
beatintR/W再生開始からの拍数。代入するとその位置へシークする — 「音楽的な時間」を参照
barintR/W再生開始からの小節数。代入するとその位置へシークする
phasefloatR現在の拍の中での進み具合(0.0〜1.0)
beats_per_barintR1小節の拍数
frames_to_beatintR次の拍までのフレーム数
tempofloatR/W再生速度の倍率 — 「テンポ」を参照
bpmintRファイルが示す1分あたりの拍数

loop

entity.music.loop = True

有効にすると、トラックは終了後に自動的に再スタートします。BGMでは一般的です。


volume

entity.music.volume = 0.5

0.0未満の値は0.0にクランプされます。


pan

entity.music.pan = -1.0  # 完全に左
entity.music.pan =  1.0  # 完全に右
entity.music.pan =  0.0  # 中央

-1.0から1.0の範囲にクランプされます。


time

t = entity.music.time

読み取り専用。現在の再生位置(秒)。


duration

d = entity.music.duration

読み取り専用。トラックの全体の長さ(秒)。


finished

if entity.music.finished:
    world.destroy(entity)

読み取り専用。トラックの再生が完了するとTrue(loopがFalseの場合)。


チャンネル

music = entity.music
for i in range(len(music.channels)):
    print(i, music.channels[i].volume)

music.channels[1].volume = 0.0    # パート1をミュートする

トラッカーモジュールやMIDIファイルは、1つのクロックで演奏される独立したパートの集まりです。music.channels はパートごとに1つのハンドルを返し、それぞれが次のプロパティを持ちます。

プロパティ型R/W説明
indexintRチャンネル番号
volumefloatR/Wこのパートだけのゲイン(0.0〜1.0 にクランプ)

すべてのチャンネルは 1.0 から始まります。すべてのパートが1つの再生ヘッドを共有するため、どれかの音量を上げ下げしても、他のパートとずれることはありません。サウンドトラックの盛り上がりを変えるのにこれを使います。アクションが激しくなったらパートを足し、プレイヤーがやられたら外す、といった具合です。

チャンネルが何を指すかはフォーマットによって異なります。

フォーマットチャンネル
トラッカーモジュールモジュールのチャンネルごとに1つ(一般的な .mod は4つ)
MIDI常に16個。MIDIチャンネルごとに1つ。ファイルが使わないチャンネルは無音になるだけ
OGGなし: len(music.channels) は 0

チャンネル番号は 0 から始まります。範囲外の番号を指定すると IndexError が発生し、music.channels[i] そのものには代入できず、volume にだけ代入できます。負のインデックスはPythonのリストと同じく末尾から数えます。music.channels[-1] は最後のチャンネルです。

MIDIでは、チャンネルを 0.0 にすると、そのチャンネルで鳴り続けているノートも切られます。


音楽的な時間

if music.beat != last_beat:
    last_beat = music.beat
    pulse()
プロパティ意味
beat再生開始からの拍数(整数)。単調増加です。ループしても 0 に戻らず数え続けるので、折り返しを特別扱いせずに前フレームの値と比較できます
bar再生開始からの小節数(整数)。こちらも単調増加です
phase現在の拍の中での位置。0.0 以上 1.0 未満。beat が段階的に変わるのに対し、phase は連続的です。拍と拍の間でなめらかに変化・減衰させたいものに使います
beats_per_bar1小節の拍数
frames_to_beat次の拍が来るまでのフレーム数(切り上げ)。拍の直後ではなく拍ちょうどに何かを始めたいときに使います

beat や bar への代入はシークになります。 music.bar = 8 と1回書くだけで8小節目の頭へ移動し、全チャンネルは同期したままです。次のフレームで新しい位置が読み取れます。

カウンターはファイルから時間を測るため、フォーマットによって使えるかどうかが変わります。

フォーマットbeat、bar、phase、frames_to_beat、beats_per_bar、bpm
トラッカーモジュール利用可能。下記参照
MIDI利用可能。ファイルから読み取ります。下記参照
OGG利用不可: beat、bar、beats_per_bar、frames_to_beat、bpm は -1、phase は 0.0 を返し、beat や bar への代入は何も行いません

そのため、OGGの可能性がある音楽に同期するゲームは、最初に music.beat >= 0 を確認し、-1 のときは独自のタイマーに切り替えてください。

MIDIファイルの拍

MIDIファイルは自身の音楽的な時間を記述しており、Pyxenはそれを読み取ります。1拍はファイルの四分音符で、bpm と1拍の長さは再生中のファイルのテンポ変更に従います。beats_per_bar はファイルの最初の拍子記号から四分音符単位で求めます。3/4は 3、6/8も 3 です。拍子記号のないファイルは4拍子として扱います。

MIDIトラックがループすると、カウントは次の小節の頭まで進みます。そのため beat と bar は増え続け、各周回は小節の1拍目から始まります。シークはちょうどその拍に移動します。その位置より前から鳴っていた音符は再生されません。

トラッカーモジュールの拍

モジュールには拍子の情報がないため、Pyxenはモジュールが持つ唯一の定義であるBPMを使います。トラッカーの1ティックは 2.5 / BPM 秒なので、ファイルのBPMが正しくなるのは1拍が24ティックのとき、つまりスピード6で4行、またはスピード3で8行のときです。Pyxenはこの方法で数えます。行ではなくティックで数えることで、モジュールの途中でスピードが変わっても拍が安定します。

1小節は4拍です。これはデータではなく慣習です。3/4拍子のモジュールでも beats_per_bar は 4 と報告されます。1拍あたりのティック数が異なる曲では、拍カウンターが聞こえ方と合いません。


テンポ

music.tempo = 1.5    # 50%速く、ピッチはそのまま
music.tempo = 1.0    # 通常

tempo はトラックが要求する速度に掛ける倍率です。リサンプリングは行わないためピッチは変わらず、各パートが早く、または遅く進むだけです。tempo が何であっても、bpm はファイル自身の値を報告し続けます。

制限: 最大は 8.0 です。下限は、トラッカーモジュールでは 0.25 まで。受け付けられない値は無視されます(プロパティは以前の値のままです)。MIDIは 0.05 まで受け付けます。OGGでは tempo は効果がなく、常に 1.0 が返ります。

beat、phase、frames_to_beat は tempo に追従するので、音楽に同期したゲームは、音楽を速めても同期したままです。


実践例

拍ごとのパルス

この例は、プロジェクトに theme.mod というトラッカーモジュールがあることを前提にしています。

from pyxen import *

last_beat = -1

def start():
    world.spawn(name="song", music=Music("theme", loop=True))
    world.spawn(name="pulse", x=160, y=90,
                shape=Circle(24, fill=(1.0, 0.5, 0.2, 1.0)))

def update():
    global last_beat
    music = world.get(name="song").music
    pulse = world.get(name="pulse")

    # phase は各拍の中で0から1へ進むので、拍の瞬間に大きくなり、その後落ち着く。
    s = 1.0 + 0.5 * (1.0 - music.phase)
    pulse.scale = (s, s)

    # beat は1拍につき1回だけ変わる。
    if music.beat != last_beat:
        last_beat = music.beat
        print("beat", last_beat, "bar", music.bar)

パートをミュートする

クリックするたびに、トラックの2番目のパートをオフ・オンにします。

from pyxen import *

def start():
    world.spawn(name="song", music=Music("theme", loop=True))

def update():
    music = world.get(name="song").music
    if input.mouse.left.pressed:
        part = music.channels[1]
        if part.volume > 0.0:
            part.volume = 0.0
        else:
            part.volume = 1.0

小節へジャンプする

from pyxen import *

def start():
    world.spawn(name="song", music=Music("theme", loop=True))

def update():
    music = world.get(name="song").music
    if input.mouse.left.pressed:
        music.bar = 4      # 4小節目へ直接ジャンプする

フォーマット

音楽アセットには、OGG Vorbis(.ogg)、MIDI(.mid、.midi)、またはトラッカーモジュール — ProTracker(.mod)、FastTracker II(.xm)、Impulse Tracker(.it)、Scream Tracker 3(.s3m)— を使用できます。

アセットはベース名で検索されます。Music("theme") は theme.ogg、theme.mod など、プロジェクト内に存在する拡張子のファイルを見つけます。拡張子を渡すのが最もよくある間違いです。Music("theme.ogg") は theme.ogg.ogg を探して例外を送出します。

トラッカーモジュールは、同じ曲のOGGが数メガバイトになるところを数キロバイトで収まることが多く、構造上シームレスにループします — 小さなゲームに最適です。

トラッカーモジュールは、ここで最も多くの機能を使えるフォーマットでもあります。チャンネル、拍、小節、テンポのすべてがモジュールで動作します。MIDIにはチャンネルとテンポがあり、OGGにはどちらもありません(「チャンネル」と「音楽的な時間」の節を参照)。

モジュールはパンニングに対応しています。ProTrackerの 8xx(パン設定)コマンドで、曲の再生中にチャンネルをステレオ空間内で動かせます。

Sound(WAV、完全にメモリに読み込み)と異なり、Musicはストリーミングされオンデマンドでデコードされます — 長いオーディオにより適しています。


よくあるエラー

ファイル名の指定なし

Music()
ValueError: Music must have a file

無効なアセット

Music("nonexistent")
Music("theme.ogg")     # 拡張子はファイル側に付けます。名前には含めません
ValueError: Music 'nonexistent' not found. Use the asset name without its extension (.ogg, .mid, .mod, .xm, .it, .s3m).