この記事では、レトロスタイルのゲーム用のサウンドを生成できるシンセサイザーベースのオーディオエンジンを作成する方法を学びます。 サウンドエンジンは実行時にすべてのサウンドを生成し、MP3やWAVファイルなどの外部依存関係を必要としません。 最終的に、ゲームに便利に埋め込むことができる作業ライブラリを取得します。
オーディオエンジンの作成を開始する前に、いくつかの概念に対処する必要があります。 まず、エンジンが音を生成するために使用する波について。 次に、音波がデジタル形式でどのように保存されラベル付けされるかを理解する必要があります。
このチュートリアルでは、ActionScript 3.0プログラミング言語を使用しますが、使用する手法と概念は、低レベルオーディオAPIへのアクセスを提供する他の言語に簡単に変換できます。
波
作成するオーディオエンジンは、4つの基本的な種類の波(基本的な形式が定期的に繰り返されるため、 周期的な波とも呼ばれます)を使用します。 それらはすべて、アナログシンセサイザーとデジタルシンセサイザーの両方で非常に頻繁に使用されます。 各波形には独自のサウンド特性があります。
以下は、各波形の視覚的表現、サウンドの例、および各波形をサンプリングされたデータの配列として生成するために必要なコードです。
パルス
パルス波は、シャープで調和の取れた音を作成します。
MP3をダウンロードします。
配列のパルス波(-1.0〜1.0の範囲)を表す値を生成するには、次のコードを使用できます
n
は配列を満たすのに必要な値の数、
a
は配列、
p
は波内の正規化された位置です。
var i:int = 0; var n:int = 100; var p:Number; while( i < n ) { p = i / n; a[i] = p < 0.5 ? 1.0 : -1.0; i ++; }
見た
のこぎり波は、シャープで耳障りな音を作ります。
MP3をダウンロードします。
ノコギリ波(-1.0から1.0の範囲)を表す値の配列を生成するには、
n
は配列を満たすのに必要な値の数、
a
は配列、
p
は波内の正規化された位置です。
var i:int = 0; var n:int = 100; var p:Number; while( i < n ) { p = i / n; a[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0; i ++; }
正弦波
正弦波は、滑らかでクリアなサウンドを作成します。
MP3をダウンロードします。
正弦波(-1.0〜1.0の範囲)を表す値の配列を生成するには、次のコードを使用できます
n
は配列を満たすために必要な値の数、
a
は配列、
p
は波内の正規化された位置です。
var i:int = 0; var n:int = 100; var p:Number; while( i < n ) { p = i / n; a[i] = Math.sin( p * 2.0 * Math.PI ); i ++; }
トライアングル
三角波は滑らかで調和のとれた音を作ります。
MP3をダウンロードします。
三角波(-1.0から1.0の範囲)を表す値の配列を生成するには、次のコードを使用できます
n
は配列を満たすために必要な値の数、
a
は配列、
p
は波内の正規化された位置です。
var i:int = 0; var n:int = 100; var p:Number; while( i < n ) { p = i / n; a[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0; i ++; }
6行目の拡張バージョンは次のとおりです。
if (p < 0.25) { a[i] = p * 4.0; } else if (p < 0.75) { a[i] = 2.0 - (p * 4.0); } else { a[i] = (p * 4.0) - 4.0; }
波の振幅と周波数
音波には2つの重要な特性があります-波の振幅と周波数 : ボリュームとピッチはそれぞれ、それらに依存します。 振幅は波の絶対ピーク値であり、周波数は波が1秒間に繰り返す回数です。 通常、周波数はヘルツ(Hz、Hz)で測定されます。
次の図は、振幅が0.5、周波数が20 Hzののこぎり波の200ミリ秒のスナップショットを示しています。
波の周波数がピッチに直接どのように影響するかの例を示します。周波数が440 Hzの波は、現代のコンサートピアノの最初のオクターブ(A4)の標準音と同じ高さです。 この頻度を考えると、次のコードを使用して他のノートの頻度を計算できます。
f = Math.pow( 2, n / 12 ) * 440.0;
このコードの変数
n
は、A4から対象のノートまでのノートの数です。 たとえば、2オクターブ(A5)の周波数(A4より1オクターブ高い)を見つけるには、A5がA4より12音高いため、
n
を
12
に設定する必要があります。 大きなオクターブ(E2)のmi周波数を見つけるには、E2がA4の5ノート下にあるため、
n
に
-5
値を割り当てる必要があります。 また、逆の操作を行って、特定の頻度で(A4に関連する)ノートを見つけることもできます。
n = Math.round( 12.0 * Math.log( f / 440.0 ) * Math.LOG2E );
これらの計算は、音の周波数が対数であるため機能します。周波数を2倍すると音が1オクターブ上にシフトし、2で除算すると1オクターブ音が低くなります。
デジタル音波
デジタルの世界では、音波はバイナリデータとして保存する必要があり、これは通常、音波の状態(またはサンプル)の定期的なスナップショットを作成することによって行われます。 音の持続時間の1秒ごとに受信される波のサンプルの数は、サンプリング周波数と呼ばれます 。つまり、サンプリング周波数44100の音には、音の持続時間の1秒あたり44100の波のサンプルが含まれます。
以下の図は、音波をサンプリングする方法を示しています。
図の白い点は、サンプリングされてデジタル形式で保存された波の振幅ポイントを示しています。 ビットマップの解像度としてそれらを使用できます:ビットマップにピクセルが多いほど、格納できる視覚情報が多くなり、情報量が増えるとファイルサイズが大きくなります(ここでは圧縮を考慮しません)。 同じことがデジタルサウンドにも当てはまります。サウンドファイルに含まれるウェーブサンプルが多いほど、再現されるサウンドウェーブはより正確になります。
サンプリング周波数に加えて、デジタルサウンドにはビット/秒で測定されるビットレートもあります。 ビットレート(ビットレート)は、各波形サンプルの保存に使用されるバイナリビットの数によって異なります。 これは、ビットマップの各ピクセルのARGB情報を格納するために使用されるビット数に似ています。 たとえば、サンプリング周波数が44100でビットレートが705600のサウンドは、各波のサンプルを16ビット値として保存します。次のコードを使用すると、非常に簡単に計算できます。
bitsPerSample = bitRate / sampleRate;
上記の値を使用する実用的な例を次に示します。
trace( 705600 / 44100 ); // "16"
ここで最も重要なことは、サウンドサンプルとは何かを理解することです。 作成するエンジンは、生のサウンドサンプルを生成および操作します。
変調器
サウンドエンジンのプログラミングを開始する前に、アナログシンセサイザーとデジタルシンセサイザーの両方で積極的に使用されている変調器という別の概念を理解する必要があります。 本質的に、変調器は標準的な波ですが、通常は音を作成する代わりに、音波の1つまたは複数のプロパティ(つまり、振幅または周波数)を変調するために使用されます。
たとえば、 vibratoを使用します。 ビブラートは、周期的に脈動する高さの変化です。 変調器を使用してこのような効果を作成するには、正弦波の変調波を設定し、変調周波数をたとえば8 Hzに設定します。 その後、この変調器を音波の周波数に接続すると、ビブラート効果が得られます。変調器は、音波の周波数(高さ)を1秒間に8回徐々に増減させます。
作成するエンジンにより、モジュレーターをサウンドにアタッチして、さまざまなエフェクトを幅広く提供できます。
デモオーディオエンジン
このパートでは、完全なオーディオエンジンに必要なすべての基本コードを記述します。 オーディオエンジン(Flash)の簡単なデモを次に示します。demo 。
このデモでは、1つのサウンドのみが再生されますが、サウンドの周波数はランダムに変化します。 モジュレーターもサウンドに接続され、ビブラート効果を作成します(サウンドの振幅を変調することにより)。モジュレーターの周波数もランダムに変化します。
クラスAudioWaveform
最初に作成するクラスは、エンジンがサウンドを生成するために使用するウェーブの定数値を保存するだけです。
noise
という新しいクラスパッケージを作成してから、次のクラスをこのパッケージに追加します。
package noise { public final class AudioWaveform { static public const PULSE:int = 0; static public const SAWTOOTH:int = 1; static public const SINE:int = 2; static public const TRIANGLE:int = 3; } }
また、クラスに静的な一般メソッドを追加します。これは、波の値を確認するために使用できます。 このメソッドは、波の値の正確さに応じて
true
または
false
を返します。
static public function validate( waveform:int ):Boolean { if( waveform == PULSE ) return true; if( waveform == SAWTOOTH ) return true; if( waveform == SINE ) return true; if( waveform == TRIANGLE ) return true; return false; }
最後に、クラスを作成する理由がないため、クラスをインスタンス化から保護する必要があります。 これはクラスコンストラクター内で実行できます。
public function AudioWaveform() { throw new Error( "AudioWaveform class cannot be instantiated" ); }
これで、クラスの作成が完了しました。
列挙型のクラス、完全に静的なクラス、およびシングルトンクラスを直接インスタンス化しないように保護することをお勧めします。そのようなクラスのインスタンスは作成しないでください。これには理由がありません。 Javaなどの一部のプログラミング言語では、これらのタイプのクラスのほとんどでこれは自動的に行われますが、ActionScript 3.0では、クラスコンストラクター内でこの動作を強制する必要があります。
オーディオクラス
リストの次は
Audio
クラスです。 その性質上、このクラスはネイティブActionScript 3.0
Sound
クラスに似ており、各オーディオエンジンは
Audio
クラスのインスタンスとして提示されます。
次のスケルトンを
noise
パッケージに追加します。
package noise { public class Audio { public function Audio() {} } }
クラスに最初に追加する必要があるのは、サウンドを再生するときにオーディオエンジンにサウンドウェーブを生成する方法を伝えるプロパティです。 これらのプロパティは、サウンドで使用される波の種類、波の周波数と振幅、音の持続時間、減衰時間です。 これらのプロパティはすべてプライベートであり、それらへのアクセスはゲッター/セッターを介して行われます。
private var m_waveform:int = AudioWaveform.PULSE; private var m_frequency:Number = 100.0; private var m_amplitude:Number = 0.5; private var m_duration:Number = 0.2; private var m_release:Number = 0.2;
ご覧のとおり、各プロパティに適切なデフォルト値を設定しています。
amplitude
は
0.0
の範囲の値で、
frequency
Hz単位で、
duration
と
release
は秒単位です。
また、サウンドに接続されたモジュレーターのプライベートプロパティを2つ追加する必要があります。 これらのプロパティへのアクセスは、ゲッター/セッター経由でも行われます。
private var m_frequencyModulator:AudioModulator = null; private var m_amplitudeModulator:AudioModulator = null;
最後に、
Audio
クラスには、
AudioEngine
クラスのみがアクセスできるいくつかの内部プロパティが含まれている必要があります(すぐに記述します)。 これらのプロパティは、ゲッター/セッターの後ろに隠す必要はありません。
internal var position:Number = 0.0; internal var playing:Boolean = false; internal var releasing:Boolean = false; internal var samples:Vector.<Number> = null;
position
は秒単位で設定され、
AudioEngine
クラス
AudioEngine
再生中のサウンドの位置
AudioEngine
追跡できるようにします。 これは、音波サンプルを計算するために必要です。
playing
と
releasing
は、
AudioEngine
クラスにサウンドの状態
releasing
伝え、
samples
プロパティは、サウンドで使用されるキャッシュされたウェーブサンプルへの参照です。 これらのプロパティの使用方法は、
AudioEngine
クラスを記述するときにわかります。
Audio
クラスを終了するには、ゲッター/セッターを追加する必要があります。
Audio. waveform
public final function get waveform():int { return m_waveform; } public final function set waveform( value:int ):void { if( AudioWaveform.isValid( value ) == false ) { return; } switch( value ) { case AudioWaveform.PULSE: samples = AudioEngine.PULSE; break; case AudioWaveform.SAWTOOTH: samples = AudioEngine.SAWTOOTH; break; case AudioWaveform.SINE: samples = AudioEngine.SINE; break; case AudioWaveform.TRIANGLE: samples = AudioEngine.TRIANGLE; break; } m_waveform = value; }
Audio. frequency
[Inline] public final function get frequency():Number { return m_frequency; } public final function set frequency( value:Number ):void { // frequency 1.0 - 14080.0 m_frequency = value < 1.0 ? 1.0 : value > 14080.0 ? 14080.0 : value; }
Audio. amplitude
[Inline] public final function get amplitude():Number { return m_amplitude; } public final function set amplitude( value:Number ):void { // amplitude 0.0 - 1.0 m_amplitude = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value; }
Audio. duration
[Inline] public final function get duration():Number { return m_duration; } public final function set duration( value:Number ):void { // duration 0.0 - 60.0 m_duration = value < 0.0 ? 0.0 : value > 60.0 ? 60.0 : value; }
Audio. release
[Inline] public final function get release():Number { return m_release; } public function set release( value:Number ):void { // release 0.0 - 10.0 m_release = value < 0.0 ? 0.0 : value > 10.0 ? 10.0 : value; }
Audio. frequencyModulator
[Inline] public final function get frequencyModulator():AudioModulator { return m_frequencyModulator; } public final function set frequencyModulator( value:AudioModulator ):void { m_frequencyModulator = value; }
Audio. amplitudeModulator
[Inline] public final function get amplitudeModulator():AudioModulator { return m_amplitudeModulator; } public final function set amplitudeModulator( value:AudioModulator ):void { m_amplitudeModulator = value; }
もちろん、いくつかのゲッター関数に関連付けられた
[Inline]
メタデータラベルに気づきます。 このメタデータラベルはActionScript 3.0コンパイラの機能であり、その名前が示すとおりに機能します。つまり、関数のコンテンツを埋め込み (拡張)します。 賢明に使用すると、この機能は最適化に非常に役立ち、最適化プログラムの実行中にダイナミックオーディオ信号を生成するタスクは間違いなく必要です。
AudioModulatorクラス
AudioModulator
の
AudioModulator
、
Audio
インスタンスの振幅と周波数を変調して、さまざまな有用なエフェクトを作成する機能を提供することです。 モジュレーターは実際には
Audio
インスタンスに似ており、波形、振幅、および周波数を持っていますが、可聴音は作成せず、他の音のみを変更します。
最初から始めましょう
noise
パッケージに次のクラススケルトンを作成します。
package noise { public class AudioModulator { public function AudioModulator() {} } }
次に、プライベートプロパティを追加します。
private var m_waveform:int = AudioWaveform.SINE; private var m_frequency:Number = 4.0; private var m_amplitude:Number = 1.0; private var m_shift:Number = 0.0; private var m_samples:Vector.<Number> = null;
これが
Audio
クラスに非常に似ていると思われる場合、間違えられません。ここでは、
shift
プロパティを除いてすべてが同じです。
shift
プロパティの機能を理解するには、オーディオエンジンで使用される基本的な波(パルス、ノコギリ波、正弦波、または三角形)の1つを覚えて、どこでも波を通る垂直線を想像してください。 この垂直線の水平位置が
shift
値になります。 この値の範囲は
0.0
、変調器に波の読み取りを開始する場所を指示します。 また、モジュレーターによってサウンドの振幅または周波数に加えられる変更に絶対的な影響を及ぼします。
たとえば、変調器が正弦波を使用して音の周波数を変調し、
shift
が
0.0
に設定されている場合、音の周波数は最初に増加し、正弦波の曲率に応じて低下します。 ただし、
shift
0.5
に設定されている場合、音の周波数は最初に減少し、したがって増加します。
さて、コードに戻ります。
AudioModulator
は、
AudioEngine
のみ使用される内部メソッドが1つ含まれています。 メソッドの形式は次のとおりです。
[Inline] internal final function process( time:Number ):Number { var p:int = 0; var s:Number = 0.0; if( m_shift != 0.0 ) { time += ( 1.0 / m_frequency ) * m_shift; } p = ( 44100 * m_frequency * time ) % 44100; s = m_samples[p]; return s * m_amplitude; }
この機能は頻繁に使用されるため、ビルトインです。また、変調器が接続されている再生音ごとに「多くの場合」「毎秒44100回」という意味です(これは、埋め込みが非常に便利です)。 この関数は、変調器が使用する波形からサウンドサンプルを受信し、サンプルの振幅を変更して、結果を返します。
AudioModulator
クラスを完了するには、ゲッター/セッターを追加します。
AudioModulator. waveform
public function get waveform():int { return m_waveform; } public function set waveform( value:int ):void { if( AudioWaveform.isValid( value ) == false ) { return; } switch( value ) { case AudioWaveform.PULSE: m_samples = AudioEngine.PULSE; break; case AudioWaveform.SAWTOOTH: m_samples = AudioEngine.SAWTOOTH; break; case AudioWaveform.SINE: m_samples = AudioEngine.SINE; break; case AudioWaveform.TRIANGLE: m_samples = AudioEngine.TRIANGLE; break; } m_waveform = value; }
AudioModulator. frequency
public function get frequency():Number { return m_frequency; } public function set frequency( value:Number ):void { // frequency 0.01 - 100.0 m_frequency = value < 0.01 ? 0.01 : value > 100.0 ? 100.0 : value; }
AudioModulator. amplitude
public function get amplitude():Number { return m_amplitude; } public function set amplitude( value:Number ):void { // amplitude 0.0 - 8000.0 m_amplitude = value < 0.0 ? 0.0 : value > 8000.0 ? 8000.0 : value; }
AudioModulator. shift
public function get shift():Number { return m_shift; } public function set shift( value:Number ):void { // shift 0.0 - 1.0 m_shift = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value; }
また、この
AudioModulator
クラスは完全と見なすことができます。
AudioEngineクラス
挑戦のために:
AudioEngine
クラス。 これは完全に静的なクラスです。
Audio
インスタンスとサウンド生成に関連するほぼすべてを管理します。
noise
のあるクラスのスケルトンから通常通り始めましょう:
package noise { import flash.events.SampleDataEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.utils.ByteArray; // public final class AudioEngine { public function AudioEngine() { throw new Error( "AudioEngine class cannot be instantiated" ); } } }
前述のように、完全に静的なクラスのインスタンスは作成しないでください。したがって、誰かがインスタンスを作成しようとすると、クラスのコンストラクターで例外がスローされます。 完全に静的なクラスを拡張する理由がないため、クラスも
final
です。
このクラスに最初に追加するのは、内部定数です。 これらの定数は、オーディオエンジンで使用される4つの各波形のサンプルをキャッシュするために使用されます。 各キャッシュには44,100個のサンプルが含まれており、これは1 Hzの波形に相当します。 これにより、オーディオエンジンは非常にクリーンで低周波の音波を作成できます。
次の定数が使用されます。
static internal const PULSE:Vector.<Number> = new Vector.<Number>( 44100 ); static internal const SAWTOOTH:Vector.<Number> = new Vector.<Number>( 44100 ); static internal const SINE:Vector.<Number> = new Vector.<Number>( 44100 ); static internal const TRIANGLE:Vector.<Number> = new Vector.<Number>( 44100 );
このクラスは、2つのプライベート定数も使用します。
static private const BUFFER_SIZE:int = 2048; static private const SAMPLE_TIME:Number = 1.0 / 44100.0;
BUFFER_SIZE
は、オーディオサンプルをリクエストするときにActionScript 3.0オーディオAPIに転送されるサウンドサンプルの数です。 これは、許可されるサンプルの最小数であり、音のレイテンシを可能な限り小さくします。 サンプルの数を増やしてCPUの負荷を減らすことができますが、これによりサウンドのレイテンシが増加します。
SAMPLE_TIME
は、1つのサウンドサンプルの長さ(秒)です。
そして今、プライベート変数:
static private var m_position:Number = 0.0; static private var m_amplitude:Number = 0.5; static private var m_soundStream:Sound = null; static private var m_soundChannel:SoundChannel = null; static private var m_audioList:Vector.<Audio> = new Vector.<Audio>(); static private var m_sampleList:Vector.<Number> = new Vector.<Number>( BUFFER_SIZE );
-
m_position
、サウンドストリーミング時間を秒単位で追跡するために使用されます。 -
m_amplitude
は、すべての再生可能なAudio
インスタンスのグローバルなセカンダリ振幅です。 -
m_soundStream
およびm_soundChannel
は説明は不要です。 -
m_audioList
は、Audio
すべての複製インスタンスへのリンクが含まれています。 -
m_sampleList
は、ActionScript 3.0オーディオAPIから要求されたサウンドサンプルを格納するために使用される一時バッファーです。
次に、クラスを初期化する必要があります。 これを行うには多くの方法がありますが、私はシンプルで簡単な静的クラスコンストラクタを好みます:
static private function $AudioEngine():void { var i:int = 0; var n:int = 44100; var p:Number = 0.0; // while( i < n ) { p = i / n; SINE[i] = Math.sin( Math.PI * 2.0 * p ); PULSE[i] = p < 0.5 ? 1.0 : -1.0; SAWTOOTH[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0; TRIANGLE[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0; i++; } // m_soundStream = new Sound(); m_soundStream.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData ); m_soundChannel = m_soundStream.play(); } $AudioEngine();
このコードでは、次のことが行われます。4つの波形のそれぞれに対してサンプルが生成およびキャッシュされ、これは1回だけ発生します。 オーディオストリームのインスタンスも作成され、アプリケーションが完了するまで開始および再生されます。
AudioEngine
クラスには、
Audio
インスタンスを再生および停止するために使用される3つの一般的なメソッドがあります。
AudioEngine. play()
static public function play( audio:Audio ):void { if( audio.playing == false ) { m_audioList.push( audio ); } // , audio.position = m_position - ( m_soundChannel.position * 0.001 ); audio.playing = true; audio.releasing = false; }
AudioEngine. stop()
static public function stop( audio:Audio, allowRelease:Boolean = true ):void { if( audio.playing == false ) { // return; } if( allowRelease ) { // audio.position = audio.duration; audio.releasing = true; return; } audio.playing = false; audio.releasing = false; }
AudioEngine. stopAll()
static public function stopAll( allowRelease:Boolean = true ):void { var i:int = 0; var n:int = m_audioList.length; var o:Audio = null; // if( allowRelease ) { while( i < n ) { o = m_audioList[i]; o.position = o.duration; o.releasing = true; i++; } return; } while( i < n ) { o = m_audioList[i]; o.playing = false; o.releasing = false; i++; } }
そして、ここでは、サウンド処理の基本的な方法に進みます。各方法はプライベートです。
AudioEngine. onSampleData()
static private function onSampleData( event:SampleDataEvent ):void { var i:int = 0; var n:int = BUFFER_SIZE; var s:Number = 0.0; var b:ByteArray = event.data; // if( m_soundChannel == null ) { while( i < n ) { b.writeFloat( 0.0 ); b.writeFloat( 0.0 ); i++; } return; } // generateSamples(); // while( i < n ) { s = m_sampleList[i] * m_amplitude; b.writeFloat( s ); b.writeFloat( s ); m_sampleList[i] = 0.0; i++; } // m_position = m_soundChannel.position * 0.001; }
そのため、最初の構成
if
で
m_soundChannel
は、まだnullであるかどうかを確認します。これが必要なのは
SAMPLE_DATA
、メソッドが呼び出されると、
m_soundStream.play()
メソッドがインスタンスを返す機会を得る前であっても、イベントがすぐにディスパッチされるため
SoundChannel
です。
ループ
while
は、要求されたサウンドサンプルをバイパス
m_soundStream
し、インスタンスに書き込みます
ByteArray
。サウンドサンプルは、次の方法で生成されます。
AudioEngine. generateSamples()
static private function generateSamples():void { var i:int = 0; var n:int = m_audioList.length; var j:int = 0; var k:int = BUFFER_SIZE; var p:int = 0; var f:Number = 0.0; var a:Number = 0.0; var s:Number = 0.0; var o:Audio = null; // audio while( i < n ) { o = m_audioList[i]; // if( o.playing == false ) { // audio m_audioList.splice( i, 1 ); n--; continue; } // j = 0; // while( j < k ) { if( o.position < 0.0 ) { // audio o.position += SAMPLE_TIME; j++; continue; } if( o.position >= o.duration ) { if( o.position >= o.duration + o.release ) { // audio o.playing = false; j++; continue; } // audio o.releasing = true; } // audio f = o.frequency; a = o.amplitude; // if( o.frequencyModulator != null ) { // f += o.frequencyModulator.process( o.position ); } // if( o.amplitudeModulator != null ) { // a += o.amplitudeModulator.process( o.position ); } // p = ( 44100 * f * o.position ) % 44100; // s = o.samples[p]; // if( o.releasing ) { // s *= 1.0 - ( ( o.position - o.duration ) / o.release ); } // m_sampleList[j] += s * a; // audio o.position += SAMPLE_TIME; j++; } i++; } }
最後に、すべてを完了するために、プライベート変数のゲッター/セッターを追加する必要があります
m_amplitude
。
static public function get amplitude():Number { return m_amplitude; } static public function set amplitude( value:Number ):void { // amplitude 0.0 - 1.0 m_amplitude = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value; }
オーディオプロセッサのデモ
このパートでは、ベースエンジンにオーディオプロセッサを追加し、単純な遅延プロセッサを作成します。このデモは、動作中の遅延プロセッサー(Flash)を示しています:demo。
このデモでは、1つの音のみが再生されますが、音の周波数はランダムに変化し、エンジンによって生成されたサンプルは遅延プロセッサーを通過し、フェーディングエコー効果を作成します。
クラスAudioProcessor
最初に行うことは、オーディオプロセッサの基本クラスを作成することです。
package noise { public class AudioProcessor { // public var enabled:Boolean = true; // public function AudioProcessor() { if( Object(this).constructor == AudioProcessor ) { throw new Error( "AudioProcessor class must be extended" ); } } // internal function process( samples:Vector.<Number> ):void {} } }
ご覧のとおり、このクラスは非常に単純で、サンプルの処理が必要なときに
process()
クラスによって呼び出される内部メソッドと、プロセッサのオンとオフを切り替えるために使用できる
AudioEngine
一般的なプロパティ
enabled
が含まれています。
AudioDelayクラス
クラス
AudioDelay
は、音の遅れ自体を作成するクラスです。彼はクラスを広げてい
AudioProcessor
ます。以下は、使用する空のクラスのスケルトンです。
package noise { public class AudioDelay extends AudioProcessor { // public function AudioDelay( time:Number = 0.5 ) { this.time = time; } } }
time
クラスコンストラクターに渡される引数は、遅延シーケンスの時間(秒単位)、つまり各サウンド遅延間の時間です。
プライベートプロパティを追加しましょう。
private var m_buffer:Vector.<Number> = new Vector.<Number>(); private var m_bufferSize:int = 0; private var m_bufferIndex:int = 0; private var m_time:Number = 0.0; private var m_gain:Number = 0.8;
ベクトル
m_buffer
はフィードバックループです。メソッド
process
に送信されるすべてのサウンドサンプルが含まれ、これらのサンプルは
m_bufferIndex
バッファーを通過する間、常に変更されます(この場合、振幅が減少します)。これは、メソッドに到達したときに意味があり
process()
ます。
プロパティ
m_bufferSize
と
m_bufferIndex
は、バッファのステータスを監視するために使用されます。プロパティ
m_time
は、秒単位の遅延時間です。プロパティ
m_gain
は、バッファリングされたサウンドサンプルの振幅を経時的に減少させるために使用される要因です。
このクラスにはメソッドが1つしかなく、クラス内のメソッド
process()
をオーバーライドする内部メソッドです。
process()
AudioProcessor
internal override function process( samples:Vector.<Number> ):void { var i:int = 0; var n:int = samples.length; var v:Number = 0.0; // while( i < n ) { v = m_buffer[m_bufferIndex]; // v *= m_gain; // v += samples[i]; // // m_buffer[m_bufferIndex] = v; m_bufferIndex++; // if( m_bufferIndex == m_bufferSize ) { m_bufferIndex = 0; } // samples[i] = v; i++; } }
最後に、我々は、プライベートプロパティのゲッター/セッターを追加する必要があります
m_time
と
m_gain
:
public function get time():Number { return m_time; } public function set time( value:Number ):void { // time 0.0001 - 8.0 value = value < 0.0001 ? 0.0001 : value > 8.0 ? 8.0 : value; // time , if( m_time == value ) { return; } // time m_time = value; // m_bufferSize = Math.floor( 44100 * m_time ); m_buffer.length = m_bufferSize; }
public function get gain():Number { return m_gain; } public function set gain( value:Number ):void { // gain 0.0 - 1.0 m_gain = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value; }
信じられないかもしれませんが、これでクラスは
AudioDelay
終わりです。実際、フィードバックループ(プロパティ
m_buffer
)の仕組みを理解していれば、サウンド遅延の実装は非常に簡単です。
クラスの更新 AudioEngine
最後に行う
AudioEngine
ことは、オーディオプロセッサを追加できるようにクラスを更新することです。まず、オーディオプロセッサのインスタンスを保持するベクトルを追加しましょう。
static private var m_processorList:Vector.<AudioProcessor> = new Vector.<AudioProcessor>();
実際にクラス
AudioEngine
にプロセッサを追加および削除するには、2つの一般的なメソッドも使用する必要があります。
AudioEngine. addProcessor()
static public function addProcessor( processor:AudioProcessor ):void { if( m_processorList.indexOf( processor ) == -1 ) { m_processorList.push( processor ); } }
AudioEngine. removeProcessor()
static public function removeProcessor( processor:AudioProcessor ):void { var i:int = m_processorList.indexOf( processor ); if( i != -1 ) { m_processorList.splice( i, 1 ); } }
すべてが非常に簡単です。これらのメソッドはすべて
AudioProcessor
、ベクターにインスタンスを追加および削除します
m_processorList
。
最後に追加するメソッドは、オーディオプロセッサのリストを調べ、プロセッサがオンになっている場合、サウンドメソッドをプロセッサメソッドに送信します
process()
。
static private function processSamples():void { var i:int = 0; var n:int = m_processorList.length; // while( i < n ) { if( m_processorList[i].enabled ) { m_processorList[i].process( m_sampleList ); } i++; } }
コードの最後の部分を追加するときが来ましたが、これがプライベート
onSampleData()
クラスメソッドに追加する必要がある唯一の行です
AudioEngine
。
if( m_soundChannel == null ) { while( i < n ) { b.writeFloat( 0.0 ); b.writeFloat( 0.0 ); i++; } return; } // generateSamples(); processSamples(); // while( i < n ) { s = m_sampleList[i] * m_amplitude; b.writeFloat( s ); b.writeFloat( s ); m_sampleList[i] = 0.0; i++; }
コード行をクラスに追加します
processSamples();
。
processSamples()
先ほど追加したメソッドを呼び出すだけです。
おわりに
実際、それがすべてです。チュートリアルの最初の部分では、さまざまな波形と、音波をデジタル形式で保存する方法に注目しました。次に、ベースのオーディオエンジンコードを作成し、オーディオプロセッサを追加しました。
このコードを使用するとさらに多くのことができますが、実行時にオーディオエンジンが実行する必要がある作業全体を忘れないようにすることが重要です。エンジンをあまりにも高度に(非常に簡単に)すると、ゲーム全体のパフォーマンスが低下する可能性があります-オーディオエンジンを別のストリーム(またはワーカーActionScript 3.0)に転送した場合でも、正しく実装されていないと、CPU時間の大部分を占有します。
ただし、多くのプロフェッショナルゲームおよびそれほど専門的ではないゲームは、実行時にサウンド処理のほとんどを実行します。これは、動的なサウンドエフェクトと音楽がゲームプレイを大幅に豊かにし、プレイヤーがゲームの世界を深く掘り下げることができるためです。作成したオーディオエンジンは、ファイルから読み込まれたサウンドエフェクトの通常の(生成されていない)サンプルを簡単に処理できます。本質的に、最も単純な形式のすべてのデジタルサウンドはサンプルのシーケンスです。
別の側面を検討する価値があります。サウンドはゲームの重要な部分であり、視覚的なコンポーネントと同様に重要かつ強力です。ゲームの品質が心配な場合は、開発の最後の瞬間にゲームに落としたりねじ込んだりしないでください。音の設計に時間をかけると、気付かれることはありません。
チュートリアルを楽しんで、そこから何か役に立つことを学べることを願っています。ゲームのサウンドについてもう少し考えたとしても、私の仕事は無駄になっていると思います。
すべてのソースコードオーディオエンジンはここからダウンロードできます。
楽しんでください!