2016/12/13(火) [n年前の日記]
#1 [ruby][gosu] Ruby + Gosu で音が鳴らせるかテストしてみる
Windows 10 x64 + Ruby + Gosu で、サウンドが鳴らせるか動作確認してみますよ。ちょっと問題があることが分かったけど、そのあたりは後で説明するということで。…ドキュメントを読み落としてるところがありそうなので、間違ったことを書いてる可能性もありますが。
動作確認環境は、Windows10 x64 + Ruby 2.2.6 p396 mingw32 + gosu 0.10.8。
動作確認環境は、Windows10 x64 + Ruby 2.2.6 p396 mingw32 + gosu 0.10.8。
◎ ワンショットでSEを鳴らしてみる。 :
まずは、SE(Sound Effect の略、効果音)をワンショット(一発だけ)鳴らしてみましょうか。
以下のサンプルは、一定フレーム数(60フレーム=1秒)が経ったら次々にSEを鳴らす、という処理をやってます。
_sound_test.rb
使用したサウンドデータファイルは以下。自作データなので、CC0 / Public Domain にしておきます。ちと数が多いので、まとめてzipにして置いときます。
_tmp_se_all.zip
バラバラにDLしたい方は以下をどうぞ。
_tmp_se_01.wav
_tmp_se_02.wav
_tmp_se_03.wav
_tmp_se_01.ogg
_tmp_se_02.ogg
_tmp_se_03.ogg
さて。ruby sound_test.rb で実行。
鳴りましたな。
一応解説。
Ruby + Gosu でSEを鳴らすには、Sampleクラスを使います。…「Sound」クラスじゃないのですな。おそらく、波形データ(サンプリングデータ)を扱うからSample、なのでしょう。
サウンドファイルの読み込みは、以下のような感じ。
再生する時は、play() を使うようで。
扱えるサウンドファイルの種類ですが、手元の環境で試したところ、wav ファイルとoggファイルが使えるようでした。ただ、自分の環境はDTMもやってる関係で何か余計なものを入れていて、故にoggが使えてる可能性もあるかもなと。他の環境でも ogg が使えるかどうかはちと分からんです。そこはそれぞれ試してもらうしか。
以下のサンプルは、一定フレーム数(60フレーム=1秒)が経ったら次々にSEを鳴らす、という処理をやってます。
_sound_test.rb
require 'gosu' class MyWindow < Gosu::Window def initialize super 320, 240, false self.caption = 'Sound Play Test' # wavファイルを読み込む @sounds = [] [ "tmp_se_01.wav", "tmp_se_02.wav", "tmp_se_03.wav", "tmp_se_01.ogg", "tmp_se_02.ogg", "tmp_se_03.ogg" ].each do |fn| @sounds.push(Gosu::Sample.new(fn)) end @cnt = 0 end def update if @cnt % 60 == 0 # 一定フレーム数が過ぎたらサウンドを再生 n = (@cnt / 60) % @sounds.length @sounds[n].play end @cnt += 1 end def draw end def button_down(id) # ESCキーを押したらウインドウを閉じる close if id == Gosu::KbEscape end end window = MyWindow.new window.show
使用したサウンドデータファイルは以下。自作データなので、CC0 / Public Domain にしておきます。ちと数が多いので、まとめてzipにして置いときます。
_tmp_se_all.zip
バラバラにDLしたい方は以下をどうぞ。
_tmp_se_01.wav
_tmp_se_02.wav
_tmp_se_03.wav
_tmp_se_01.ogg
_tmp_se_02.ogg
_tmp_se_03.ogg
さて。ruby sound_test.rb で実行。
鳴りましたな。
一応解説。
Ruby + Gosu でSEを鳴らすには、Sampleクラスを使います。…「Sound」クラスじゃないのですな。おそらく、波形データ(サンプリングデータ)を扱うからSample、なのでしょう。
サウンドファイルの読み込みは、以下のような感じ。
@sound = Gosu::Sample.new("サウンドファイル名")
再生する時は、play() を使うようで。
@sound.play
扱えるサウンドファイルの種類ですが、手元の環境で試したところ、wav ファイルとoggファイルが使えるようでした。ただ、自分の環境はDTMもやってる関係で何か余計なものを入れていて、故にoggが使えてる可能性もあるかもなと。他の環境でも ogg が使えるかどうかはちと分からんです。そこはそれぞれ試してもらうしか。
◎ SEの音量を変えたり再生速度を変えたり再生停止してみたりする。 :
_Sampleクラスのドキュメント
を眺めた感じでは、Sample#play() は音量指定、再生速度指定、ループ再生指定ができるようで。
また、ゲーム制作用ライブラリとしては、SE再生後に任意のタイミングで再生停止できないとゲーム用として使えなかったりするので、そのあたりも動作確認してみます。
とりあえず、各キーを押すと再生だの停止だのをするサンプルを書いてみました。キーの割り当ては以下。
_sound_test2.rb
使用したサウンドファイルは以下。これも自作データなので CC0 / Public Domain ってことで。
_tmp_voice.wav
ruby sound_test2.rb で実行。
鳴りました。音量指定、再生速度指定、ループ再生もできているし、停止、ポーズ、ポーズ再開、音量変更、再生中かどうかの判別、ポーズ中かどうかの判別もできているようで。
少々解説。
Sample#play() は、以下の指定ができます。
停止や音量変更については…。Sample#play() を呼ぶと、SampleInstance なるクラスを返すようで、そのクラスをどこかに取っておいて、ソレを使って停止や音量変更をする、という手順になっているようです。
そういえば、ドキュメントには「SampleInstanceを使い終わったらすぐに破棄しろよな。じゃないと何がどうなるか知らねえぞ」と怖いことが書いてあるのですが…。Rubyでその破棄とやらはどうやったらできるのでしょうか。nil でも再代入しとけばいいのだろうか…。Rubyは全く詳しくないから、分からん…。
ところで、上記のサンプルでちょっとハマった点が。最初は、MyWindowクラスの update() の中で、
何のことはない。update() の中で button_down?() を使うと、そのキーが押されてる間は何回も何回も処理されてしまうわけで。キーを押した瞬間だけ処理をするわけじゃない、てなあたりに気づくのが遅れて結構ハマりました…。
このあたり、DXRuby では、「キーが押された瞬間」「キーが押されてるかどうか」「キーを離した瞬間」、それぞれを判別できる機能があるので便利だったのだなと再認識した次第。
また、ゲーム制作用ライブラリとしては、SE再生後に任意のタイミングで再生停止できないとゲーム用として使えなかったりするので、そのあたりも動作確認してみます。
とりあえず、各キーを押すと再生だの停止だのをするサンプルを書いてみました。キーの割り当ては以下。
- Aキー : 通常再生
- Bキー : 音量を小さくして再生
- Cキー : 低速再生
- Dキー : 高速再生
- Eキー : ループ再生
- Pキー : ポーズ/ポーズ解除
- Vキー : 音量を0に
- Zキー : 再生停止
_sound_test2.rb
require 'gosu' class MyWindow < Gosu::Window def initialize super 320, 240, false self.caption = 'Sound Play Test' @fnt = Gosu::Font.new(20) # wavファイルを読み込む @sound = Gosu::Sample.new("tmp_voice.wav") @si = nil # SampleInstance @play_msg = "" @pause_msg = "" end def update if @si if @si.playing? # 再生中? @play_msg = "Playing" else @play_msg = "Stopped" end if @si.paused? # ポーズ中? @pause_msg = "Paused" else @pause_msg = "Not Paused" end else # まだ鳴らしてない @play_msg = "Not Play Start" @pause_msg = "Not Play Start" end end def draw @fnt.draw("Push A,B,C,D,E,P,V and Z", 4, 4, 0) # 再生状態、ポーズ状態を表示 @fnt.draw(@play_msg, 4, 30, 0) @fnt.draw(@pause_msg, 4, 50, 0) end def button_down(id) close if id == Gosu::KbEscape end # キーが離された時に呼ばれる def button_up(id) @si = @sound.play if id == Gosu::KbA # 通常再生 @si = @sound.play(0.2, 1, false) if id == Gosu::KbB # 音量小 @si = @sound.play(1, 0.5, false) if id == Gosu::KbC # 再生速度を遅く @si = @sound.play(1, 2.0, false) if id == Gosu::KbD # 再生速度を早く @si = @sound.play(1, 1, true) if id == Gosu::KbE # ループ再生 if @si if id == Gosu::KbZ if @si.playing? # 再生中? @si.stop # サウンドを停止 end elsif id == Gosu::KbP if @si.paused? # ポーズ中? @si.resume # ポーズ解除 elsif @si.playing? # 再生中? @si.pause # ポーズ(一時停止) end elsif id == Gosu::KbV if @si.playing? @si.volume = 0 # ボリュームを0に end end end end end window = MyWindow.new window.show
使用したサウンドファイルは以下。これも自作データなので CC0 / Public Domain ってことで。
_tmp_voice.wav
ruby sound_test2.rb で実行。
鳴りました。音量指定、再生速度指定、ループ再生もできているし、停止、ポーズ、ポーズ再開、音量変更、再生中かどうかの判別、ポーズ中かどうかの判別もできているようで。
少々解説。
Sample#play() は、以下の指定ができます。
@sound.play(音量, 再生速度, ループ再生)
- 音量は、1.0が本来の音量。値を小さくすると音量が小さくなる。
- 再生速度は、1.0が標準速度。値を大きくすると再生速度が速くなり、値を小さくすると再生速度が遅くなる。
- ループ再生は、true / false で指定する。true ならループ再生。
停止や音量変更については…。Sample#play() を呼ぶと、SampleInstance なるクラスを返すようで、そのクラスをどこかに取っておいて、ソレを使って停止や音量変更をする、という手順になっているようです。
@si = @sound.play ... @si.stop # 再生停止 @si.pause # ポーズ(一時停止) @si.resume # ポーズ解除 @si.volume = 0 # 音量変更 @si.playing? # 再生中か否かを true / false で返す @si.paused? # ポーズ中か否かを true / false で返す
そういえば、ドキュメントには「SampleInstanceを使い終わったらすぐに破棄しろよな。じゃないと何がどうなるか知らねえぞ」と怖いことが書いてあるのですが…。Rubyでその破棄とやらはどうやったらできるのでしょうか。nil でも再代入しとけばいいのだろうか…。Rubyは全く詳しくないから、分からん…。
ところで、上記のサンプルでちょっとハマった点が。最初は、MyWindowクラスの update() の中で、
@si = @sound.play if button_down?(Gosu::KbA)みたいな感じで書いてたのですけど、それだと停止その他が一切できなくて悩んでしまって。
何のことはない。update() の中で button_down?() を使うと、そのキーが押されてる間は何回も何回も処理されてしまうわけで。キーを押した瞬間だけ処理をするわけじゃない、てなあたりに気づくのが遅れて結構ハマりました…。
このあたり、DXRuby では、「キーが押された瞬間」「キーが押されてるかどうか」「キーを離した瞬間」、それぞれを判別できる機能があるので便利だったのだなと再認識した次第。
◎ BGMを再生してみる。 :
SEを再生する時は Sampleクラスを使いますが、BGMを鳴らしたい時は Songクラスを使ってくれ、と
_ドキュメント
に書いてあったので、試してみます。
_song_test.rb
_tmp_loop01.ogg
ruby song_test.rb で実行。
鳴ることは鳴ったけど、ここで問題発生。ループ再生が綺麗に繋がってない…。曲がブチッと切れてる…。どうも最後まで再生し終わる前に、頭に戻ってしまっているように聞こえますな。
一応解説。
BGMファイル(wavファイル、oggファイル)を読み込むのは、以下のように書きます。
再生、停止、ポーズは以下。
SE再生と違って、PAN変更、再生速度の変更はできないよ、とドキュメントには書いてありますな。音量変更はできるみたいだから、フェードアウト処理はできそうな予感。
それにしても、ループ再生ができないのは痛いなあ…。
このあたり、以前も遭遇したような。 _2013/04/30 頃に、StarRuby と HSP で似たような症状を見ましたよ。たしか StarRuby は SDL を使ってた気がするんですけど違いましたっけ。Gosu は SDL2 を使ってるらしいですが、どうもSDL関係はこのあたりに代々不具合でもあるのかな、という気がしてきました。
まあ、PCエンジンのCD-ROMゲームのように、1曲が結構長いデータを用意して誤魔化す手もあるのですけど。
とまあ、そんな感じで若干問題はありつつも、Ruby + Gosu で一応サウンドは鳴らせるようではありますね。
これで、画像表示、キー入力、サウンド再生ができることが分かったから…。これならリアルタイム2Dゲームの類も、一応は作れそうかなと。たぶん。何かトラップが隠れてそうな不安もありますが。
_song_test.rb
require 'gosu' class MyWindow < Gosu::Window def initialize super 320, 240, false self.caption = 'Song (BGM) Play Test' @fnt = Gosu::Font.new(20) # BGMのoggファイルを読み込む @bgm = Gosu::Song.new("tmp_loop01.ogg") @play_msg = "" @pause_msg = "" end def update # 再生中? if @bgm.playing? @play_msg = "Playing" else @play_msg = "Stopped" end # ポーズ中? if @bgm.paused? @pause_msg = "Paused" else @pause_msg = "Not Paused" end end def draw # 再生中/停止中, ポーズ中/非ポーズを表示 @fnt.draw("Push A:Play, Z:Stop, X:Pause", 4, 4, 0) @fnt.draw(@play_msg, 4, 30, 0) @fnt.draw(@pause_msg, 4, 50, 0) end def button_down(id) close if id == Gosu::KbEscape end # キーが離された時に呼ばれる def button_up(id) @bgm.play(true) if id == Gosu::KbA # 通常再生(ループ有効) @bgm.stop if id == Gosu::KbZ # 再生停止 @bgm.pause if id == Gosu::KbX # ポーズ end end window = MyWindow.new window.show使用したBGMファイル(.ogg)は以下。ループ再生ができるように、曲の終わりと頭がちゃんと繋がるデータになってます。なってるはずです。これも自作データなので CC0 / Public Domain ってことで。
_tmp_loop01.ogg
ruby song_test.rb で実行。
鳴ることは鳴ったけど、ここで問題発生。ループ再生が綺麗に繋がってない…。曲がブチッと切れてる…。どうも最後まで再生し終わる前に、頭に戻ってしまっているように聞こえますな。
一応解説。
BGMファイル(wavファイル、oggファイル)を読み込むのは、以下のように書きます。
@bgm = Gosu::Song.new("BGMファイル名")
再生、停止、ポーズは以下。
@bgm.play(true) # 再生開始(ループ有効) @bgm.stop # 再生停止 @bgm.pause # ポーズplay(true)でループ再生、play(false)で非ループ再生のようです。また、ポーズ解除は play()を呼べばいいようです。
SE再生と違って、PAN変更、再生速度の変更はできないよ、とドキュメントには書いてありますな。音量変更はできるみたいだから、フェードアウト処理はできそうな予感。
それにしても、ループ再生ができないのは痛いなあ…。
このあたり、以前も遭遇したような。 _2013/04/30 頃に、StarRuby と HSP で似たような症状を見ましたよ。たしか StarRuby は SDL を使ってた気がするんですけど違いましたっけ。Gosu は SDL2 を使ってるらしいですが、どうもSDL関係はこのあたりに代々不具合でもあるのかな、という気がしてきました。
まあ、PCエンジンのCD-ROMゲームのように、1曲が結構長いデータを用意して誤魔化す手もあるのですけど。
とまあ、そんな感じで若干問題はありつつも、Ruby + Gosu で一応サウンドは鳴らせるようではありますね。
これで、画像表示、キー入力、サウンド再生ができることが分かったから…。これならリアルタイム2Dゲームの類も、一応は作れそうかなと。たぶん。何かトラップが隠れてそうな不安もありますが。
[ ツッコむ ]
#2 [prog][ruby][gosu] SE再生についての余談
余談ですが。
たとえレトロ風の2Dゲーム制作であっても、SE再生後、「そのSEが再生中かどうかを判別する機能」や「再生中のSEを停止する機能」は、ゲーム制作ライブラリに必須だよな、と。
例えばの話。
プレイヤーキャラが必殺技を撃って、「はどーけん!」とか「しょーりゅーけん!」とか「たつまきせんぷうきゃく!」とか叫ぶとするじゃないですか。
必殺技を撃って「はどーけん!」が再生されてる途中で、敵がパンチを繰り出してプレイヤーキャラに当たったとします。プレイヤーキャラは「うわっ!」と叫びながら吹っ飛びますわな。
この時、SE関係はどういう処理になるか。「はどーけん!」が『再生中かどうかを判別』して、もし再生中なら「はどーけん」の『再生を停止』しないといけない。そこでようやく「うわっ!」の再生要求を続けて送れる状態になるわけです。
これがもし、「特定の音声が再生中かどうかを判別」する機能も無ければ、「音声を停止」する機能も無かったらどんな事態になるか。
「はどーけん!」という声と一緒に「うわっ!」という声も聞こえてしまうわけですよ。お前一体何人居るんだよ! それともいっこく堂かよ! いや待て、彼でもそんな喋り方は無理だよ! …まあ、こんな作りは許されないですよね。
昔話ですけど、メガドライブの「ベアナックル3」が、そんな感じの酷い実装で…。プレイヤーキャラがダメージ受けて吹っ飛んでるのに「いじょーとーさん!」と必殺技の音声だけが孤独に威勢良く流れ続けて。 *1 もうね。萎えまくりですよ。激萎えですよ。せっかくのベルトスクロールアクションの爽快感がゼロ。サウンド周りの実装が糞なせいで全編通して何もかもが台無し。ベアナックル2は名作扱いされてるのに、ベアナックル3は若干どこか無かったこと扱いになってるのは、あのサウンド周りの糞な実装が大きいのだろうなあ…と個人的には思うわけで。
ということで。SE再生中かどうかの判別と、SE再生の停止。この2つはいくらなんでも最低限実装されてないとマズいわけで。この2つすら実装されてないゲーム制作ライブラリは、なんちゃってゲーム制作ライブラリです。お前はゲーム制作ライブラリを名乗れねえよ、ゲーム制作を舐めてんじゃねえぞ、と。
なので、ゲーム制作ライブラリを評価する際、まずはこの2つがあるかどうかをチェックすれば、ちゃんと考えながら作られているライブラリか、それとも見た目だけそれっぽくした実用にならないインチキライブラリか、ひとまずすぐに分かるのかもなあ、てな気分になってきました。
まあ、余談でした。
たとえレトロ風の2Dゲーム制作であっても、SE再生後、「そのSEが再生中かどうかを判別する機能」や「再生中のSEを停止する機能」は、ゲーム制作ライブラリに必須だよな、と。
例えばの話。
プレイヤーキャラが必殺技を撃って、「はどーけん!」とか「しょーりゅーけん!」とか「たつまきせんぷうきゃく!」とか叫ぶとするじゃないですか。
必殺技を撃って「はどーけん!」が再生されてる途中で、敵がパンチを繰り出してプレイヤーキャラに当たったとします。プレイヤーキャラは「うわっ!」と叫びながら吹っ飛びますわな。
この時、SE関係はどういう処理になるか。「はどーけん!」が『再生中かどうかを判別』して、もし再生中なら「はどーけん」の『再生を停止』しないといけない。そこでようやく「うわっ!」の再生要求を続けて送れる状態になるわけです。
これがもし、「特定の音声が再生中かどうかを判別」する機能も無ければ、「音声を停止」する機能も無かったらどんな事態になるか。
「はどーけん!」という声と一緒に「うわっ!」という声も聞こえてしまうわけですよ。お前一体何人居るんだよ! それともいっこく堂かよ! いや待て、彼でもそんな喋り方は無理だよ! …まあ、こんな作りは許されないですよね。
昔話ですけど、メガドライブの「ベアナックル3」が、そんな感じの酷い実装で…。プレイヤーキャラがダメージ受けて吹っ飛んでるのに「いじょーとーさん!」と必殺技の音声だけが孤独に威勢良く流れ続けて。 *1 もうね。萎えまくりですよ。激萎えですよ。せっかくのベルトスクロールアクションの爽快感がゼロ。サウンド周りの実装が糞なせいで全編通して何もかもが台無し。ベアナックル2は名作扱いされてるのに、ベアナックル3は若干どこか無かったこと扱いになってるのは、あのサウンド周りの糞な実装が大きいのだろうなあ…と個人的には思うわけで。
ということで。SE再生中かどうかの判別と、SE再生の停止。この2つはいくらなんでも最低限実装されてないとマズいわけで。この2つすら実装されてないゲーム制作ライブラリは、なんちゃってゲーム制作ライブラリです。お前はゲーム制作ライブラリを名乗れねえよ、ゲーム制作を舐めてんじゃねえぞ、と。
なので、ゲーム制作ライブラリを評価する際、まずはこの2つがあるかどうかをチェックすれば、ちゃんと考えながら作られているライブラリか、それとも見た目だけそれっぽくした実用にならないインチキライブラリか、ひとまずすぐに分かるのかもなあ、てな気分になってきました。
まあ、余談でした。
*1: 自分の中ではそんな印象が残ってるのだけど、偽記憶だろうか…。さすがにダメージ受けた時は音声を停止してたかなあ…。攻撃が敵に当たっても時々無音になってたことはハッキリと覚えてるんですけど…。
[ ツッコむ ]
#3 [pc] マウスを交換
今まで使ってたマウス、ELECOM M-XG1UBBK のホイール回転が妙な感じになってきて。1回分回しただけなのに連続でトリガーが送られる感じ。ロータリーエンコーダが寿命なんだろうなと。
予備の ELECOM M-XG1UBSV を発掘してきて交換。
今まで使ってた個体は、 _2015/12/01 に購入して使い始めたらしい。すると1年も持ったのか…。ELECOM製品なのに…。って去年も同じこと書いてるな…。
予備の ELECOM M-XG1UBSV を発掘してきて交換。
今まで使ってた個体は、 _2015/12/01 に購入して使い始めたらしい。すると1年も持ったのか…。ELECOM製品なのに…。って去年も同じこと書いてるな…。
[ ツッコむ ]
#4 [anime] 精霊の守り人アニメ版最終回を視聴
NHKで再放送されていた、精霊の守り人アニメ版最終回を視聴。
やっぱり名作だわ…。
やっぱり名作だわ…。
[ ツッコむ ]
以上、1 日分です。