はじめてのBlenderアドオン開発

Last Update: 2023.3.1

Blender 2.7

はじめてのBlenderアドオン開発

Blender 2.7

Last Update: 2023.3.1

3-6. オーディオファイルを再生する

本節では、Blenderが提供するオーディオファイルの再生支援モジュールであるaudモジュールを使って、オーディオファイルを再生する方法を説明します。 そもそも、3DCGを作成するBlenderでオーディオファイルを扱うことに疑問があると思いますが、このようなAPIも用意されているのだという気持ちで本節を読んでもらえればと思います。

作成するアドオンの仕様

オーディオの再生方法と再生中のオーディオ設定を変更する方法を理解するため、次の機能を持つアドオンを作成します。

アドオンを作成する

1-5節 を参考にして以下のソースコードを入力し、ファイル名 sample_3_6.py として保存してください。

import bpy
from bpy.props import StringProperty, FloatProperty
import aud


bl_info = {
    "name": "サンプル3-6: 指定したオーディオファイルを再生する",
    "author": "Nutti",
    "version": (2, 0),
    "blender": (2, 75, 0),
    "location": "3Dビュー > ツール・シェルフ > オーディオ再生",
    "description": "オーディオファイルを再生するアドオン",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "3D View"
}


class AudioDevice():

    device = None       # サウンドデバイス
    factory = None      # サウンドファクトリ
    handle = None       # サウンドハンドラ


# 音量を設定
def set_volume(self, value):
    self['paf_volume'] = value
    if AudioDevice.handle is not None:
        AudioDevice.handle.volume = value


# 設定されている音量を取得
def get_volume(self):
    return self.get('paf_volume', 0.5)


# オーディオファイルの選択
class SelectAudioFile(bpy.types.Operator):

    bl_idname = "view_3d.select_audio_file"
    bl_label = "オーディオファイルの選択"
    bl_description = "再生するオーディオファイルを選択します"

    filepath = StringProperty(subtype="FILE_PATH")      # ファイルパス
    # 検索フィルタ
    filter_glob = StringProperty(
        default="*.wav;*.mp3",
        options={'HIDDEN'}
    )

    def execute(self, context):
        sc = context.scene

        # 初回時のみサウンドデバイスを作成
        if AudioDevice.device is None:
            AudioDevice.device = aud.device()
        # サウンドファクトリを作成
        AudioDevice.factory = aud.Factory(self.filepath)
        # オーディオファイルを再生
        AudioDevice.handle = AudioDevice.device.play(AudioDevice.factory)
        AudioDevice.handle.volume = sc.paf_volume

        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        # ファイルブラウザ表示
        wm.fileselect_add(self)

        return {'RUNNING_MODAL'}


# 再生停止
class StopAudioFile(bpy.types.Operator):

    bl_idname = "view_3d.stop_audio_file"
    bl_label = "オーディオファイルの再生を停止"
    bl_description = "オーディオファイルの再生を停止します"

    def execute(self, context):
        if AudioDevice.handle is None:
            return {'CANCELLED'}
        # 再生停止
        AudioDevice.handle.stop()

        return {'FINISHED'}


# ツールシェルフに「オーディオ再生」タブを追加
class VIEW3D_PT_PlayAudioFileMenu(bpy.types.Panel):

    bl_label = "オーディオ再生"          # タブに表示される文字列
    bl_space_type = 'VIEW_3D'           # メニューを表示するエリア
    bl_region_type = 'TOOLS'            # メニューを表示するリージョン
    bl_category = "オーディオ再生"       # タブを開いたメニューのヘッダーに表示される文字列

    # メニューの描画処理
    def draw(self, context):
        layout = self.layout
        sc = context.scene

        if AudioDevice.handle is not None:
            # 再生中の状態
            if AudioDevice.handle.status:
                layout.operator(StopAudioFile.bl_idname, text="停止", icon='X')
            # 再生を停止した状態
            else:
                layout.operator(
                    SelectAudioFile.bl_idname, text="オーディオファイルを選択",
                    icon='PLAY'
                )
        else:
            # ファイルブラウザを表示する
            layout.operator(
                SelectAudioFile.bl_idname, text="オーディオファイルを選択",
                icon='PLAY'
            )
        layout.prop(sc, "paf_volume", text="音量")


# プロパティを初期化
def init_props():
    sc = bpy.types.Scene
    sc.paf_volume = FloatProperty(
        name="音量",
        description="音量を調整します",
        default=0.4,
        max=1.0,
        min=0.0,
        get=get_volume,
        set=set_volume
    )


# プロパティを削除
def clear_props():
    sc = bpy.types.Scene
    del sc.paf_volume


def register():
    bpy.utils.register_module(__name__)
    init_props()
    print("サンプル3-6: アドオン「サンプル3-6」が有効化されました。")


def unregister():
    clear_props()
    bpy.utils.unregister_module(__name__)
    print("サンプル3-6: アドオン「サンプル3-6」が無効化されました。")


if __name__ == "__main__":
    register()

アドオンを使用する

アドオンを有効化する

1-5節 を参考に作成したアドオンを有効化すると、コンソールウィンドウに以下の文字列が出力されます。

サンプル3-6: アドオン「サンプル3-6」が有効化されました。

[3Dビュー] エリアのツール・シェルフを表示し、[オーディオ再生] タブが追加されていることを確認します。

アドオンの機能を使用する

有効化したアドオンの機能を使い、動作を確認します。

1 [3Dビュー] エリアのツール・シェルフから、[オーディオ再生] タブを選択します。
2 [オーディオファイルを選択] ボタンをクリックします。
3 ファイルブラウザが開くため、再生するオーディオファイルを選択して オーディオファイルの選択 ボタンをクリックします。本節のサンプルでは、デフォルトで.wavと.mp3の2つの拡張子に絞って表示していますが、[ファイルのフィルタリングを有効化ボタン] をクリックすることで、他のファイルも選択できるようになります。
※図では空のディレクトリを表示しています。
4 [音量] の値を変更することで、オーディオの再生音量を変更することができます。
5 [停止] ボタンをクリックすることで、オーディオの再生を停止することができます。

アドオンを無効化する

1-5節 を参考にして有効化したアドオンを無効化すると、コンソールウィンドウに以下の文字列が出力されます。

サンプル3-6: アドオン「サンプル3-6」が無効化されました。

ソースコードの解説

本節までに説明した内容は省略し、オーディオファイルの再生に関することを中心に説明します。 サンプルのソースコードに関して、ポイントとなる点を次に示します。

audモジュールのインポート

Blenderは aud モジュールと呼ばれる、オーディオファイルを扱うためのモジュールを提供しています。 オーディオファイルを扱うためには、この aud モジュールをインポートする必要があります。

import aud

アドオン内で利用するプロパティを定義する

複数のクラス間で共有するプロパティを次に示します。

変数 意味
paf_volume オーディオの再生音量

また本節のサンプルでは、オーディオ再生で使う次のような変数を AudioDevice クラスのクラス変数としてまとめています。bpy.types.Scene に保存せずにクラス変数としてアクセスするのは、後述するオーディオファイルの音量調整を再生中に行うためです。

クラス変数 意味
device サウンドデバイス
factory サウンドファクトリ
handle サウンドハンドラ

聞き慣れない用語がたくさん出てきましたが、サンプルのソースコードの解説の中で説明しますので、ここでは特に深く考えずに AudioDevice クラスにはオーディオ再生に必要なクラス変数が含まれている、ということだけを理解しておいてください。

オーディオファイルの選択

オーディオファイルを再生する処理を説明する前に、再生するオーディオファイルを選択する処理について解説します。 オーディオファイルの選択処理に関するオペレーションクラスは、SelectAudioFile です。 2-10節 で説明したファイルブラウザを表示するための処理と基本的に同じですが、2点異なるところがあります。

1つ目は、ファイル選択時に呼び出される execute メソッドの処理です。 本節のサンプルでは、ファイルを開いた後にオーディオファイルを再生する処理を行います。 2-10節 で宣言していたクラス変数 filenamedirectoryexecute メソッドでは利用していないため、宣言していません。

2つ目は、ファイルブラウザを開いた時に表示されるファイルが、wavファイルとmp3のファイルのみであることです。

# 検索フィルタ
filter_glob = StringProperty(
    default="*.wav;*.mp3",
    options={'HIDDEN'}
)

特定のファイルのみをファイルブラウザで表示する場合は、filter_glob クラス変数を StringProperty クラスで定義し、表示するファイルのリストを引数 default; (セミコロン)区切りで指定します。 また、必要に応じて正規表現を使うこともできます。 本節のサンプルではwavファイルとmp3ファイル(それぞれ [.wav] と [.mp3] の拡張子を持つファイル)を表示するため、*.wav; *.mp3 を指定しています。

StringProperty クラスの引数 options{'HIDDEN'} を指定していますが、これは、[ファイルブラウザ] エリアのツール・シェルフのオプションに filter_glob の項目が表示されないようにするためです。 試しに、引数 options{'HIDDEN'} を指定しないようにソースコードを書き換えて保存し、オーディオファイルの選択 ボタンをクリックしてファイルブラウザを開いてみましょう。 ツール・シェルフのオプションに filter_glob の項目が表示されるはずです。

オーディオファイルの再生

ファイルブラウザで選択したオーディオファイルの再生処理について説明します。 オーディオファイルの再生処理は次の手順に従って行います。

  1. サウンドデバイスの作成
  2. サウンドファクトリの作成
  3. サウンドハンドラの作成(オーディオファイルの再生)

ここでサウンドデバイス、サウンドファクトリ、サウンドハンドラという3つの用語が出てきましたので、簡単に説明します。 サウンドデバイスは、OpenALやSDLなどのサウンドライブラリで扱うデバイスのことを指し、オーディオを再生するために最低限必要なものです。 サウンドデバイスがないとそもそも音を出すことができません。 サウンドファクトリは、複数の音源をミックスしたり、ハイパスフィルター(HPF)やローパスフィルター(LPF)などのエフェクトをかけたりする時に必要なオブジェクトです。 出力サウンドを編集する時に使うことができますが、Blenderはあくまで3DCGソフトなので本節のサンプルではこれらの機能を使っていません。 サウンドハンドラは、再生/停止/一時停止/再生再開といった再生制御や、ピッチやボリュームなどの再生時の振る舞いを変更するためのオブジェクトです。

ファイルブラウザでファイルを選択すると、SelectAudioFile クラスの execute メソッドが実行され、選択したオーディオファイルが再生されます。 それでは、オーディオファイルを再生するための具体的なコードを見てみましょう。

def execute(self, context):
    sc = context.scene

    # 初回時のみサウンドデバイスを作成
    if AudioDevice.device is None:
        AudioDevice.device = aud.device()
    # サウンドファクトリを作成
    AudioDevice.factory = aud.Factory(self.filepath)
    # オーディオファイルを再生
    AudioDevice.handle = AudioDevice.device.play(AudioDevice.factory)
    AudioDevice.handle.volume = sc.paf_volume

    return {'FINISHED'}

1. サウンドデバイスの作成

オーディオファイルの再生処理手順に従い、最初にサウンドデバイスを作成します。 サウンドデバイスは、aud.device 関数を呼び出すことで作成することができます。 サウンドデバイスは一度作成したあとは、再生するオーディオファイルを変更しても再度作り直す必要がないため、サンプルでは最初に作成したサウンドデバイスを使いまわしています。 このため、再生停止後にオーディオを再度再生した場合、すでにサウンドデバイスが作られていたら、新たにサウンドデバイスを作成しないような処理となっています。 aud.device 関数の戻り値は、AudioDevice.device に代入します。

2. サウンドファクトリの作成

サウンドファクトリは、aud.Factory 関数を実行することで作成することができます。 aud.Factory 関数は、再生するオーディオファイルのパスを引数に指定します。 サウンドデバイスと異なり、サウンドファクトリは再生するオーディオファイルを変更するたびに作成し直す必要があります。 aud.Factory 関数の戻り値は、AudioDevice.factory に代入します。

3. サウンドハンドラの作成(オーディオファイルの再生)

サウンドファクトリ AudioDevice.factory を、サウンドデバイスAudioDevice.deviceplay メソッドの引数に指定することで、サウンドハンドラが作成されてオーディオファイルが再生されます。 作成したサウンドハンドラは AudioDevice.handle に代入します。

最後に、オーディオファイル再生時の音量を設定するために、AudioDevice.handle のメンバ変数である volume に [音量] プロパティに指定された値 sc.paf_volume を代入します。 本節のサンプルでは、音量を調整できる AudioDevice.handle のメンバ変数 volume しか利用していませんが、AudioDevice.handle は次のようなメンバ変数を提供しています。

メンバ変数 意味
volume 音量(最大1、最小0)
pitch ピッチ(最小0)
loop_count ループ回数(ループ回数を指定、ループ回数が0なら1回限りの再生、負の値なら無限ループ再生)
position 再生位置(単位は秒)
status 再生状態

オーディオファイルの再生停止

[停止] ボタンが押された時は StopAudioFile クラスの execute メソッドが呼び出され、オーディオファイルの再生を停止する処理が実行されます。

def execute(self, context):
    if AudioDevice.handle is None:
        return {'CANCELLED'}
    # 再生停止
    AudioDevice.handle.stop()

    return {'FINISHED'}

サウンドハンドラである AudioDevice.handle は、再生状態を制御するための次のような関数を用意しています。 サンプルでは、オーディオファイルの再生を停止するために AudioDevice.handle.stop 関数利用します。

関数 意味
stop オーディオファイルの再生停止
pause オーディオファイルの再生一時停止
resume オーディオファイルの再生再開

オーディオファイルの再生状態

本節のサンプルでは、オーディオファイルの再生状態に応じてUIを変更する処理が、パネルクラス VIEW3D_PT_PlayAudioFileMenudraw メソッドに存在します。 オーディオが再生停止中の場合には [オーディオファイルを選択] ボタンを表示し、再生中の場合には [停止] ボタンを表示します。

# 再生中の状態
if AudioDevice.handle.status:
    layout.operator(StopAudioFile.bl_idname, text="停止", icon='X')
# 再生を停止した状態
else:
    layout.operator(
        SelectAudioFile.bl_idname, text="オーディオファイルを選択",
        icon='PLAY'
    )

オーディオファイルの再生状態に応じてUIを変更するために、オーディオファイルの再生状態を知る必要があります。 オーディオファイルの再生状態は AudioDevice.handle.status の値から判断できるため、AudioDevice.handle.status の値に応じて表示するUIを決定します。 AudioDevice.handle.status は次に示すようにブーリアンの値が代入されます。

意味
True オーディオファイルを再生中、または再生一時停止中(AudioDevice.handle.pause が呼ばれた)
False オーディオファイルを再生停止(AudioDevice.handle.stop が呼ばれた)、または最後まで再生完了

AudioDevice.handle.statusTrue であればオーディオファイルを再生中であるため、[停止] ボタンを表示します。 一方、False であれば再生停止中であるため、[オーディオファイルを選択] ボタンを表示します。 なお、AudioDevice.handleNone の場合は、[オーディオファイルを選択] ボタンを表示しています。 この条件判定がないと、if AudioDevice.handle.status: 条件判定で、「AudioDevice.handleNoneType だから status というメンバ変数はない」とエラーが発生してしまい、UIの表示処理が中断してしまいます。

プロパティ変更の検知

サンプルでは、[3Dビュー] エリアのツール・シェルフのタブ [オーディオ再生] の [音量] プロパティからユーザが音量を変更したことを検知し、再生音量を変更しています。 ここまで読み進めてきた方は、どのようにこのような処理を実現するのか疑問に思うかもしれません。 これまでプロパティを利用した処理としては、2-3節 で示したプロパティ値の設定直後に同じ処理を再実行する場合と、3-4節 で示したプロパティの値をユーザが設定した後に処理を行う場合の2通りであったため、プロパティが変更されたことを検知する必要がありませんでした。 しかし本節のサンプルでは、一度オーディオファイルの再生を開始したあとは、オーディオファイルの再生処理がアドオンの処理とは非同期に行われることになります。 つまり、プロパティの値を参照した後に処理を行うこれまでの方法ではうまくいきません。

本節のサンプルのように、プロパティの値が変わったことを検知して独自の処理を行いたい場合は、プロパティクラスの引数 setget に、実行したい関数をプロパティクラス作成時に登録します。 引数 set には、プロパティの値が変更された時に呼び出す関数を指定し、引数 get にはプロパティの値を参照するときに呼び出す関数を指定します。サンプルのプロパティの定義を見てみましょう。

sc.paf_volume = FloatProperty(
    name="音量",
    description="音量を調整します",
    default=0.4,
    max=1.0,
    min=0.0,
    get=get_volume,
    set=set_volume
)

プロパティクラス FloatProperty の引数 getget_volume 関数、引数 setset_volume 関数を指定しています。 最初に、get_volume 関数について説明します。

# 設定されている音量を取得
def get_volume(self):
    return self.get('paf_volume', 0.5)

get_volume 関数は、第1引数に bpy.context.scene が指定された状態で呼び出されます。 bpy.type.Scenebpy.context.scene は同一です。 このため、init_props 関数で bpy.type.Scene にプロパティクラスを登録すると、プロパティクラスを登録した変数へは bpy.context.scene['paf_volume'] つまり self['paf_volume'] としてアクセスすることができます。

get_volume 関数は、プロパティを参照する時に呼び出されるため、予期しない時に呼び出される可能性があります。 このため、get_volume 関数が呼び出されるタイミングによっては、参照したい変数が存在しないかもしれません。 例えば、init_props 関数が呼ばれていないために、sc.paf_volume が作られていない場合が考えられます。 そこで、self.get メソッドの第2引数にデフォルト値を指定し、第1引数に指定したインスタンス変数が存在しない場合は、デフォルト値を返すようにします。

続いて、set_volume 関数について説明します。 set_volume 関数は、第1引数に bpy.context.scene、ユーザがプロパティに設定した値が第2引数に渡されて呼び出されます。

# 音量を設定
def set_volume(self, value):
    self['paf_volume'] = value
    if AudioDevice.handle is not None:
        AudioDevice.handle.volume = value

set_volume 関数は、self['paf_volume'] = value によりプロパティの値を更新した後、AudioDevice.handle.volume に値を設定することで音量を変更します。 このような一連の処理を行うことで、ユーザが [音量] プロパティを変更したことを検知し、オーディオファイルの再生音量を変更することができます。

なお、bpy.type.Scene に登録したプロパティクラス以外のクラスは、引数 getset に指定した関数からアクセスすることができません。 プロパティクラス以外のクラスにアクセスするためには、アクセス対象がグローバル変数やクラス変数である必要があります。 アドオン内で共通利用するプロパティを、AudioDevice クラスのクラス変数に定義したのはこのためです。

まとめ

Blenderのaudモジュールを使って、オーディオファイルを再生する方法を説明しました。 筆者がaudモジュールの存在を初めて知ったとき、3DCGソフトであるBlenderにオーディオファイルを扱うAPIがなぜ存在しているのが理解できず、本書の話題として取り上げるか迷いました。 しかし、4-1節 で紹介する公式のAPIリファレンスを参照していただければわかると思いますが、audモジュールはStandaloneモジュールに含まれています。 このため、ここでaudモジュールについて取り上げることが少し場違いであることを承知のうえで、ここで説明しました。 ただやはりaudモジュールは音を扱うため、ゲームエンジン関連のカテゴリであるGame Engine Modulesに含めたほうが理にかなっている気がします。 筆者の憶測ですが、動画作成時にオーディオファイルを再生することを考えてゲームエンジンだけに限定しなかったのかもしれません。

本節のサンプルではオーディオファイルの再生/停止の処理に加え、ユーザが変更した音量を検知する方法やファイルブラウザで表示するファイルをフィルタリングする方法を説明しました。 audモジュールはマイナーで機能が少ないモジュールのように見えますが、ローパスフィルターやハイパスフィルター、ミックスなどサウンドプログラミングをする上で基本的な機能がAPIとして提供されています。 扱いも比較的簡単であるため、ぜひこの機会にBlenderでサウンドプログラミングに挑戦してみてはいかがでしょうか。

ポイント