はじめての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」が無効化されました。
本節までに説明した内容は省略し、オーディオファイルの再生に関することを中心に説明します。 サンプルのソースコードに関して、ポイントとなる点を次に示します。
Blenderは aud
モジュールと呼ばれる、オーディオファイルを扱うためのモジュールを提供しています。 オーディオファイルを扱うためには、この aud
モジュールをインポートする必要があります。
import aud
複数のクラス間で共有するプロパティを次に示します。
変数 | 意味 |
---|---|
paf_volume |
オーディオの再生音量 |
また本節のサンプルでは、オーディオ再生で使う次のような変数を AudioDevice
クラスのクラス変数としてまとめています。bpy.types.Scene
に保存せずにクラス変数としてアクセスするのは、後述するオーディオファイルの音量調整を再生中に行うためです。
クラス変数 | 意味 |
---|---|
device |
サウンドデバイス |
factory |
サウンドファクトリ |
handle |
サウンドハンドラ |
聞き慣れない用語がたくさん出てきましたが、サンプルのソースコードの解説の中で説明しますので、ここでは特に深く考えずに AudioDevice
クラスにはオーディオ再生に必要なクラス変数が含まれている、ということだけを理解しておいてください。
オーディオファイルを再生する処理を説明する前に、再生するオーディオファイルを選択する処理について解説します。 オーディオファイルの選択処理に関するオペレーションクラスは、SelectAudioFile
です。 2-10節 で説明したファイルブラウザを表示するための処理と基本的に同じですが、2点異なるところがあります。
1つ目は、ファイル選択時に呼び出される execute
メソッドの処理です。 本節のサンプルでは、ファイルを開いた後にオーディオファイルを再生する処理を行います。 2-10節 で宣言していたクラス変数 filename
や directory
は execute
メソッドでは利用していないため、宣言していません。
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
の項目が表示されるはずです。
ファイルブラウザで選択したオーディオファイルの再生処理について説明します。 オーディオファイルの再生処理は次の手順に従って行います。
ここでサウンドデバイス、サウンドファクトリ、サウンドハンドラという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'}
オーディオファイルの再生処理手順に従い、最初にサウンドデバイスを作成します。 サウンドデバイスは、aud.device
関数を呼び出すことで作成することができます。 サウンドデバイスは一度作成したあとは、再生するオーディオファイルを変更しても再度作り直す必要がないため、サンプルでは最初に作成したサウンドデバイスを使いまわしています。 このため、再生停止後にオーディオを再度再生した場合、すでにサウンドデバイスが作られていたら、新たにサウンドデバイスを作成しないような処理となっています。 aud.device
関数の戻り値は、AudioDevice.device
に代入します。
サウンドファクトリは、aud.Factory
関数を実行することで作成することができます。 aud.Factory
関数は、再生するオーディオファイルのパスを引数に指定します。 サウンドデバイスと異なり、サウンドファクトリは再生するオーディオファイルを変更するたびに作成し直す必要があります。 aud.Factory
関数の戻り値は、AudioDevice.factory
に代入します。
サウンドファクトリ AudioDevice.factory
を、サウンドデバイスAudioDevice.device
の play
メソッドの引数に指定することで、サウンドハンドラが作成されてオーディオファイルが再生されます。 作成したサウンドハンドラは 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_PlayAudioFileMenu
の draw
メソッドに存在します。 オーディオが再生停止中の場合には [オーディオファイルを選択] ボタンを表示し、再生中の場合には [停止] ボタンを表示します。
# 再生中の状態
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.status
が True
であればオーディオファイルを再生中であるため、[停止] ボタンを表示します。 一方、False
であれば再生停止中であるため、[オーディオファイルを選択] ボタンを表示します。 なお、AudioDevice.handle
が None
の場合は、[オーディオファイルを選択] ボタンを表示しています。 この条件判定がないと、if AudioDevice.handle.status:
条件判定で、「AudioDevice.handle
は NoneType
だから status
というメンバ変数はない」とエラーが発生してしまい、UIの表示処理が中断してしまいます。
サンプルでは、[3Dビュー] エリアのツール・シェルフのタブ [オーディオ再生] の [音量] プロパティからユーザが音量を変更したことを検知し、再生音量を変更しています。 ここまで読み進めてきた方は、どのようにこのような処理を実現するのか疑問に思うかもしれません。 これまでプロパティを利用した処理としては、2-3節 で示したプロパティ値の設定直後に同じ処理を再実行する場合と、3-4節 で示したプロパティの値をユーザが設定した後に処理を行う場合の2通りであったため、プロパティが変更されたことを検知する必要がありませんでした。 しかし本節のサンプルでは、一度オーディオファイルの再生を開始したあとは、オーディオファイルの再生処理がアドオンの処理とは非同期に行われることになります。 つまり、プロパティの値を参照した後に処理を行うこれまでの方法ではうまくいきません。
本節のサンプルのように、プロパティの値が変わったことを検知して独自の処理を行いたい場合は、プロパティクラスの引数 set
と get
に、実行したい関数をプロパティクラス作成時に登録します。 引数 set
には、プロパティの値が変更された時に呼び出す関数を指定し、引数 get
にはプロパティの値を参照するときに呼び出す関数を指定します。サンプルのプロパティの定義を見てみましょう。
sc.paf_volume = FloatProperty(
name="音量",
description="音量を調整します",
default=0.4,
max=1.0,
min=0.0,
get=get_volume,
set=set_volume
)
プロパティクラス FloatProperty
の引数 get
に get_volume
関数、引数 set
に set_volume
関数を指定しています。 最初に、get_volume
関数について説明します。
# 設定されている音量を取得
def get_volume(self):
return self.get('paf_volume', 0.5)
get_volume
関数は、第1引数に bpy.context.scene
が指定された状態で呼び出されます。 bpy.type.Scene
と bpy.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
に登録したプロパティクラス以外のクラスは、引数 get
や set
に指定した関数からアクセスすることができません。 プロパティクラス以外のクラスにアクセスするためには、アクセス対象がグローバル変数やクラス変数である必要があります。 アドオン内で共通利用するプロパティを、AudioDevice
クラスのクラス変数に定義したのはこのためです。
Blenderのaudモジュールを使って、オーディオファイルを再生する方法を説明しました。 筆者がaudモジュールの存在を初めて知ったとき、3DCGソフトであるBlenderにオーディオファイルを扱うAPIがなぜ存在しているのが理解できず、本書の話題として取り上げるか迷いました。 しかし、4-1節 で紹介する公式のAPIリファレンスを参照していただければわかると思いますが、audモジュールはStandaloneモジュールに含まれています。 このため、ここでaudモジュールについて取り上げることが少し場違いであることを承知のうえで、ここで説明しました。 ただやはりaudモジュールは音を扱うため、ゲームエンジン関連のカテゴリであるGame Engine Modulesに含めたほうが理にかなっている気がします。 筆者の憶測ですが、動画作成時にオーディオファイルを再生することを考えてゲームエンジンだけに限定しなかったのかもしれません。
本節のサンプルではオーディオファイルの再生/停止の処理に加え、ユーザが変更した音量を検知する方法やファイルブラウザで表示するファイルをフィルタリングする方法を説明しました。 audモジュールはマイナーで機能が少ないモジュールのように見えますが、ローパスフィルターやハイパスフィルター、ミックスなどサウンドプログラミングをする上で基本的な機能がAPIとして提供されています。 扱いも比較的簡単であるため、ぜひこの機会にBlenderでサウンドプログラミングに挑戦してみてはいかがでしょうか。
get
、更新されたときに処理を実行したい場合は、プロパティクラスの作成時の引数 set
に実行したい関数を指定する