アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

C# で音楽再生 4

C# で音楽再生を行ってみるシリーズその 4。

今回は Bass.Net の機能を利用してイコライザを実装する。Bass.Net にも 3 バンド イコライザのサンプルが付いてくるのだが、この記事では iTunes や Songbird のような 10 バンド イコライザに挑戦したい。

サンプル プログラム

プログラムを実行すると以下のようになる。前面の小さなウィンドウがイコライザである。モードレスで作成しているので、イコライザを表示しながらプレイヤー部分の操作を行えるようになっている。

サンプル プログラム]

スクリーンショットの曲は Mike Oldfield の CRISES から。

Mike といえば Tubular Bells や Ommadawn のような大作をイメージするが Platinum 以降のポップ期も実に味わい深い。収録曲の一つである Moonlight Shadow は彼の代表的なヒット曲であり後の Tubular Bells III でもセルフ カバーされているので、彼自身にとっても重要なアルバムなのだろう。

プロジェクト一式は以下。ビルドには Visual Studio 2008 SP1 と Bass.Net、プログラムの実行には NET Framework 3.5 SP1 が必要となる。

イコライザとは?

イコライザとは音声信号の特定の周波数帯域を変化させて音作りをするための機能である。例えば中音をカットして低・高音を増幅させればザクザクとしたドンシャリ サウンド、中音を強調すれば歌モノに向く音質が得られる。

大抵のオーディオ プレイヤーに搭載されているから、どこかで触れたことがあるだろう。人によっては拘りのレシピを持っていたり、どんな曲でも通用する汎用的なiTunesのイコライザ設定のような記事が書かれたりもしていたりする。なかなか奥深いものである。

色々なイコライザ。上から iTunes、Songbird、WinAmp。

イコライザの実装

Bass.Net におけるイコライザは Un4seen.Bass.AddOn.Fx に分類されている 。この名前空間に定義された機能を利用する場合は bassfx.dll が必要なので、Un4seen の BASS ライブラリのページから事前に入手しておく。サンプル プロジェクトには bassfx.dll 2.4.4.1 を同梱している。

Bass.Net のヘルプには API の解説に必要とする BASS のネイティブ モジュールが併記されているので、新機能を使う場合は必ず目を通しておこう。

イコライザの使用を開始する前には Un4seen.Bass.AddOn.Fx.BassFx クラスの LoadMe メソッドで bass_fx.dll を読み込む。アプリ終了時などには FreeMe メソッドでそれを解放する必要がある。

// 初期化
if( !BassFx.LoadMe( "bass_fx.dll の置かれたフォルダのパス" ) )
{
    throw new Exception( "BassFx の初期化に失敗しました。" );
}

// 後始末
BassFx.FreeMe();

初期化と解放を実装したらイコライザ操作クラスを用意する。サンプルではイコライザの帯域ごとにプロパティを定義しているが、バンド数の増減へ柔軟に対応したいならコレクション化した方がよいだろう。

using Un4seen.Bass;
using Un4seen.Bass.AddOn.Fx;

namespace WpfAudioPlayer.Models
{
    /// <summary>
    /// イコライザ機能を提供します。
    /// </summary>
    class AudioEqualizer
    {
        /// <summary>
        /// ストリームにイコライザを割り当てます。
        /// </summary>
        /// <param name="stream">ストリームの識別子。</param>
        public void Attach( int stream )
        {
            this._equalizer = Bass.BASS_ChannelSetFX( stream, BASSFXType.BASS_FX_BFX_PEAKEQ, 0 );

            var eq = new BASS_BFX_PEAKEQ() { fBandwidth = AudioEqualizer.BandWidth, fQ = 0, lChannel = BASSFXChan.BASS_BFX_CHANALL };
            for( int band = 0; band < this._gains.Length; ++band )
            {
                eq.fGain = this._gains[ band ];
                eq.lBand = band;
                eq.fCenter = AudioEqualizer.GainCenters[ band ];

                Bass.BASS_FXSetParameters( this._equalizer, eq );
            }
        }

        /// <summary>
        /// イコライザを更新します。
        /// </summary>
        /// <param name="band">帯域。</param>
        /// <param name="gain">ゲイン。</param>
        private void Update( int band, float gain )
        {
            this._gains[ band ] = gain;
            var eq = new BASS_BFX_PEAKEQ() { lBand = band, fCenter = AudioEqualizer.GainCenters[ band ], fBandwidth = AudioEqualizer.BandWidth, fQ = 0, lChannel = BASSFXChan.BASS_BFX_CHANALL, fGain = gain };
            Bass.BASS_FXSetParameters( this._equalizer, eq );
        }

        /// <summary>
        /// 32 Hz のゲインを取得または設定します。
        /// </summary>
        public float Gain32
        {
            get
            {
                return this._gains[ 0 ];
            }
            set
            {
                this.Update( 0, value );
            }
        }

        // ...以下、64 ~ 16 KHz まで定義するが冗長なので割愛。

        /// <summary>
        /// 帯域幅。
        /// </summary>
        private const float BandWidth = 0.5f;

        /// <summary>
        /// 各帯域の中心周波数のコレクション。
        /// </summary>
        private static readonly int[] GainCenters = new int[ 10 ] { 32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 };

        /// <summary>
        /// Bass.Net が生成したイコライザのハンドル。
        /// </summary>
        private int _equalizer;

        /// <summary>
        /// 帯域毎のゲインのコレクション。
        /// </summary>
        private float[] _gains = new float[ 10 ] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    }
}

15 ~ 28 行目の Attach メソッドには音声ストリームのハンドルを指定する。イコライザは音声ストリーム単位で設定するようになっているため、ライフサイクルもそれに従属する。曲を変更してもパラメータを維持したいので AudioEqualizer インスタンスは一度だけ生成して Attach メソッドによりストリーム生成単位でイコライザを割り当てる設計とした。

イコライザと音声ストリームの関連づけは 17 行目の Bass.BASS_ChannelSetFX で処理。このメソッドが成功するとイコライザのハンドルが返されるので、以降のパラメータ操作のためにフィールドへ保持しておく。

19 行目で生成している BASS_BFX_PEAKEQ がイコライザのパラメータとなる。この行で各帯域に共通の設定をおこない 20 行目からのループ処理で帯域ごよの中央値などを設定している。これらの処理が完了すると音声ストリームにイコライザ設定が反映されるようになる。

以降は Gain32 などの帯域毎のプロパティを変更する度に 35 ~ 40 行目の Update メソッドが呼び出され、同様の処理により音質が変化してゆく。