C# で音楽再生 5

2010年2月14日 2 開発 , , , , , ,

C# で音楽再生を行ってみるシリーズその 5。今回は Bass.Net の機能を利用してエフェクターを実装する。

エフェクターを実装といっても、音響処理に関する専門知識はほとんど不要である。Bass.Net に用意されたクラスを適切に利用するだけで簡単に音響効果を楽しめる。

目次

サンプル プログラム

プログラムを実行すると以下のようになる。前面の小さなウィンドウがエフェクター。モードレスで作成しているので、エフェクターを表示しながらプレイヤー部分の操作を行える。また、エフェクタを見分けやすいように背景色を個別にしてみた。

サンプル プログラム

プロジェクト一式は以下となる。

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

エフェクターとは?

エフェクターとは、音声に様々な音響効果を与える機器である。原音がスピーカーや録音機器に出力されるまでの経路にエフェクターを挿入する事で音声データを変質させる。

代表的なものとしては、エコーやディレイなどの空間系、ディストーションやオーバードライブなどの歪み系、前回の記事で作成したイコライザーのような補正系などが挙げられる。

Bass.Net の場合、エフェクターの種類毎に専用のクラスが用意されているので、これらのインスタンスを音声ストリームに関連付ける事で音響効果を発生させる。

エフェクターの実装

Bass.Net に用意されたエフェクターは、FX 系 と DSP 系に大別される。前者は BASS_DX8_ というクラス名を持ち、Bass.BASS_ChannelSetFX、Bass.BASS_ChannelRemoveFX で音声ストリームとの関連付けを切り替える。後者はクラスのコンストラクタに音声ストリームのハンドルを指定する事で関連付けられ、SetBypass メソッドで有効・無効が切り替わる。

まず、これらに共通する機能として有効・無効の切り替えがあるので、これを以下のようにインターフェース化する。

/// <summary>
/// 音響効果の共通インターフェースです。
/// </summary>
interface IEffect
{
    /// <summary>
    /// 音響効果が有効である事を示す値を取得または設定します。
    /// </summary>
    bool IsEnabled { get; set; }
}

そして FX 系は以下のように実装する。

/// <summary>
/// 音声のコーラス効果を提供します。
/// </summary>
class EffectChorus : IEffect
{
    /// <summary>
    /// インスタンスを初期化します。
    /// </summary>
    /// <param name="stream">ストリームの識別子。</param>
    public EffectChorus( int stream )
    {
        this._effect = new BASS_DX8_CHORUS( EffectChorus.DefaultWetDryMix, EffectChorus.DefaultDepth, EffectChorus.DefaultFeedback, 5f, 1, EffectChorus.DefaultDelay, BASSFXPhase.BASS_FX_PHASE_NEG_90 );
        this._stream   = stream;
    }

    /// <summary>
    /// 音響効果を更新します。
    /// </summary>
    private void UpdateEffect()
    {
        if( this.IsEnabled )
        {
            Bass.BASS_FXSetParameters( this._handle, this._effect );
        }
    }

    /// <summary>
    /// 音声の遅延時間を取得または設定します。
    /// </summary>
    public float Delay
    {
        get
        {
            return this._effect.fDelay;
        }
        set
        {
            this._effect.fDelay = value;
            this.UpdateEffect();
        }
    }

    // ...以下、パラメーター毎にプロパティ定義が続く

    /// <summary>
    /// 音響効果。
    /// </summary>
    private BASS_DX8_CHORUS _effect;

    /// <summary>
    /// 音響効果の識別子。
    /// </summary>
    private int _handle;

    /// <summary>
    /// ストリームの識別子。
    /// </summary>
    private int _stream;

    #region IEffect メンバ

    /// <summary>
    /// 音響効果が有効である事を示す値を取得または設定します。
    /// </summary>
    public bool IsEnabled
    {
        get
        {
            return ( this._handle != 0 );
        }
        set
        {
            if( this._handle == 0 )
            {
                if( value )
                {
                    this._handle = Bass.BASS_ChannelSetFX( this._stream, BASSFXType.BASS_FX_DX8_CHORUS, 1 );
                    Bass.BASS_FXSetParameters( this._handle, this._effect );
                }
            }
            else if( !value )
            {
                Bass.BASS_ChannelRemoveFX( this._stream, this._handle );
                this._handle = 0;
            }
        }
    }

    #endregion
}

エフェクター用のオブジェクトをコンストラクタで生成しておき、IsEnabled の中で関連付けを切り替える。他の FX 系エフェクターも、プロパティとエフェクター用オブジェクトの型が異なるだけで、処理としてはほぼ共通である。

DSP 系の場合は、以下のように実装する。

/// <summary>
/// 音声のディレイ効果を提供します。
/// </summary>
class EffectDelay : IEffect
{
    /// <summary>
    /// インスタンスを初期化します。
    /// </summary>
    /// <param name="stream">ストリームの識別子。</param>
    public EffectDelay( int stream )
    {
        this._effector = new DSP_IIRDelay( stream, 0, 2f );
    }

    /// <summary>
    /// サンプリング数を取得または設定します。
    /// </summary>
    public int Delay
    {
        get
        {
            return this._effector.Delay;
        }
        set
        {
            this._effector.Delay = value;
        }
    }

    // ...以下、パラメーター毎にプロパティ定義が続く

    /// <summary>
    /// 音響効果。
    /// </summary>
    private DSP_IIRDelay _effector;

    #region IEffect メンバ

    /// <summary>
    /// 音響効果が有効である事を示す値を取得または設定します。
    /// </summary>
    public bool IsEnabled
    {
        get
        {
            return !this._effector.IsBypassed;

        }
        set
        {
            this._effector.SetBypass( !value );
        }
    }

    #endregion
}

FX 系と同様に IEffect インタフェースを実装しているが、その内容は異なっている。FX 系の場合、音声ストリームにエフェクトを抜き差しするイメージだが、DSP 系は繋いだままエフェクターの ON/OFF スイッチを切り替えるような感じとなる。

そして、IsBypassed と SetBypass はすり抜けるという意味になるので注意が必要だ。真偽値としては true ですり抜け、false で経由となるので、エフェクトを有効にする場合は SetBypass に false を指定しなければならない。

尚、エフェクターの各種パラメーターには適正な範囲が厳密に定められている。Bass.Net のヘルプに詳しく解説されているので、安全な実装を心掛けるなら、プロパティのセッターなどで範囲チェックを行った方が良いだろう。

サンプル プロジェクトでは入力範囲を MVVM の View の XAML に定義しているが、これは以下のように変更した方が良いかも知れない。

  • Model に上限・下限の定数を持たせる
  • ViewModel に上限・下限取得用プロパティを定義して Model の定数を返す
  • View の Slider.Maximum などは ViewModel の上限・下限プロパティにバインドする

けっこう冗長な箇所が残っており、まだまだ最適化の余地 ( 例えばパラメータ自体を ViewModel 化してエフェクタの UI はそのコレクションとして表現する、など ) はあるが、今回はここまで。

楽曲紹介

サンプル プログラムのスクリーンショットに映っている楽曲は Otis Redding の The Immortal Otis Redding から。

このアルバムは彼の作品で初めて聴いたものなので、思い入れが深い。Otis が飛行機事故で急逝した後、MG’s が録り貯められた音源から完成させたので The Immortal と名付けられている。Queen の Made in Heaven を彷彿とさせるエピソードだ。

以下は在りし日の Otis。

Otis をリスペクトしているアーティストは多く、日本では忌野清志郎が有名だ。彼の歌唱スタイルには Otis の影響が色濃いし、歌詞に登場する事もある。

また、20 年ぐらい前の月刊カドカワで Otis ゆかりの地であるメンフィスを訪れた時の事を嬉しそうに語っていたのを覚えている。確か、「エスカレーターにまでオーティスって書いてあった」とか言っていた ( Otis Elevator Company と引っかけたジョークだと思う )。

MAGMA の Christian Vander も、John Coltrane と共に Otis への憧憬を表している。死と再生をテーマにした Merci というアルバムの中で、Otis と Otis ( Ending ) という曲がワンセットになっているのも、The Immortal Otis Redding と関係していそうである。

以下は MAGMA の Otis。Christian Vander はドラムや作曲だけでなく、ボーカルも素晴らしい。

C# で音楽再生 4

2010年1月31日 0 開発 , , , , , ,

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 に分類されている 。この名前空間に定義された機能を利用する場合は bass_fx.dll が必要なので、Un4seen の BASS ライブラリのページから事前に入手しておく ( サンプル プロジェクトには bass_fx.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 メソッドが呼び出され、同様の処理により音質が変化してゆく。

C# で音楽再生 3

2010年1月9日 3 開発 , , , , , ,

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

前回まで、音楽再生には NAudio と Windows Media Format を利用していたが、今回は BASS というライブラリを試す。

  • !!注意!!
    • 2010/1/9 のサンプル プロジェクトは間違ってテスト中のイメージ ( プレイヤーが常に NAudio になる ) をアップロードしてしまったので 2010/1/10 に更新
    • また、音量調整の対象をシステム全体からストリーム固有のボリューム修正と BASS 版の再生位置のシークを実装し忘れていたので、1/27 にサンプル プロジェクトを修正

目次

サンプル プログラム

プログラムを実行すると以下のようになる。

デフォルトのオーディオ操作ライブラリは Bass.Net だが、PlayerViewModel のコンストラクタ引数で NAudio にも切り替えられるように実装したので、ステータスバーにプレイヤーの種類を表示するようにしている。

サンプル プログラム

スクリーンショットの曲は Stray Cats の BLAST OFF から。

個人的に超名盤なのだが、記事の為に引用したら Amazon レビューがないのでビックリした。やはり日本ではロカビリーはマイナーなのだろうか。

プロジェクト一式は以下となる。

サンプル プロジェクトのビルドには Visual Studio 2008 SP1、プログラムの実行には NET Framework 3.5 SP1 が必要となる。

BASS とは?

BASS とは、Un4seen Developments が提供しているオーディオ操作ライブラリである。ライセンスは非営利は無料で、商用の場合は有料 ( 詳しくは BASS のページの Licensing を参照の事 ) となる。

このライブラリは非常に豊富な機能を兼ね備えており、個人利用で非営利利用するならば、現時点で最良のオーディオ操作ライブラリと思われる。仕様については BASS のページの Main features を参考の事。

BASS そのものはネイティブなライブラリだが、Bass.Net というラッパーライブラリにより、.NET アプリケーションからも利用できるようになっている。

また、BASS は Add-on と呼ばれるプラグインによって機能拡張を行える。Add-on には WMA や AAC のようなコーデックを追加するものだけではなく、音声処理や ID3 などのタグ編集まで、実に様々なものが用意されている。

中でも凄いのが BassWinamp である。この Ad-on は WinAmp のプラグインを BASS 経由で利用可能にする。

WinAmp は長い歴史を持つオーディオ プレイヤーで、非常に豊富なプラグイン資産を持つ。BASS の Add-on に無い機能でも、WinAmp なら存在する可能性が高いので、この Add-on の使い方を覚えておくと有用である。

BASS の使用準備

初めに前述の BASS のページから、以下をダウンロードしておく。

  • Bass.Net
  • BASS 本体
  • BASSWMA などの Add-on

次に Bass.Net をインストールする。ダウンロードした zip を展開すると setup.exe が現れるので、これを起動してセットアップ作業を行う。手順については特に迷う部分はない。

途中でユーザー登録を行うと、送信したメールアドレスへ登録コードのメールが届く。必須の手順ではないが、BASS の初期化時に強制表示されるスプラッシュ スクリーンを抑止する場合は、このコードが必要になる。

Bass.Net をインストールすると、システムにアセンブリが登録される。Visual Studio のソリューション エクスプローラーの参照設定を右クリックして「参照の追加」を選ぶと、ダイアログの .NET タブ内のリストに「BASS.NET API for .Net」というコンポーネントが追加されているので、BASS を使用するプロジェクトに追加しておく。

Bass.Net は BASS の .NET 用ラッパーなので、実行には BASS 本体 ( bass.dll ) が必要となる点に注意する。BASS を使用する場合はプロジェクトの設定で、ビルド時に BASS 本体や Add-on が実行ファイルから参照できる位置にコピーされるようにしておくと安全である。

サンプル プロジェクトの場合は、BASS 本体と Add-on がビルド先の BASS フォルダにコピーされるように設定していて、BASS 初期化処理でも、このフォルダから BASS をロードするように実装している。

BASS による音声再生

サンプル プログラムの設計は MVVM 形式で設計しており、音楽再生機能は Model として位置付けている。音楽再生に必要なインターフェースは殆ど共通なので、これを IAudioPlayer として切り出している。

※前回のサンプルで既にこのインターフェースを使用していたが、今回でようやく実用的な意味を持つ事になる。

これらを実装したプレイヤークラスは PlayerViewModel が使い分けるが、インターフェースの共通性により変更は少なくて済んだ。また、PlayerView については変更自体が発生しなかった。今回のサンプル作成で、MVVM による階層化の現実的なメリットを享受する事となった。

因みにインターフェースの実装は以下のようになる。

/// <summary>
/// 音楽再生を行う為のインターフェースです。
/// </summary>
interface IAudioPlayer : IDisposable
{
    /// <summary>
    /// 再生を一時停止します。
    /// </summary>
    void Pause();

    /// <summary>
    /// 再生を開始します。
    /// </summary>
    void Play();

    /// <summary>
    /// 再生を停止します。
    /// </summary>
    void Stop();

    /// <summary>
    /// 再生位置の変更を行える事を示す値を取得します。
    /// </summary>
    bool CanSeek { get; }

    /// <summary>
    /// 再生位置を時間単位で取得または設定します。
    /// </summary>
    TimeSpan CurrentTime { get; set; }

    /// <summary>
    /// 演奏時間を取得します。
    /// </summary>
    TimeSpan Duration { get; }

    /// <summary>
    /// 音楽再生の状態を取得します。
    /// </summary>
    PlayState PlayState { get; }

    /// <summary>
    /// 音量を取得または設定します。
    /// </summary>
    float Volume { get; set; }
}

/// <summary>
/// 音楽再生の状態を表します。
/// </summary>
enum PlayState
{
    /// <summary>
    /// 再生中。
    /// </summary>
    Playing,

    /// <summary>
    /// 一時停止。
    /// </summary>
    Paused,

    /// <summary>
    /// 停止。
    /// </summary>
    Stopped
}

このインターフェースを実装した Bass.Net のオーディオ プレイヤー クラスの実装は以下のようになる。かなり長いが、削れそうな部分が無かったので、サンプルからそのままコピペした。

using System;
using System.IO;
using Un4seen.Bass;

namespace WpfAudioPlayer.Models.BassLib
{
    /// <summary>
    /// Bass.Net を利用して音楽再生を行います。
    /// </summary>
    sealed class AudioPlayer : IAudioPlayer
    {
        /// <summary>
        /// インスタンスを初期化します。
        /// </summary>
        /// <param name="fileName">音楽ファイルへのパス。</param>
        /// <exception cref="FileNotFoundException">音楽ファイルが存在しない。</exception>
        /// <exception cref="Exception">ストリームの生成に失敗した。</exception>
        public AudioPlayer( string fileName )
        {
            if( !File.Exists( fileName ) ) { throw new FileNotFoundException( fileName ); }

            // ストリーム生成
            this._handle = Bass.BASS_StreamCreateFile( fileName, 0, 0, BASSFlag.BASS_DEFAULT );
            if( this._handle == 0 )
            {
                var error = Bass.BASS_ErrorGetCode();
                throw new Exception( "ストリームの生成に失敗しました。\nError : " + error.ToString() );
            }

            // 演奏時間の算出
            {
                long    length  = Bass.BASS_ChannelGetLength( this._handle );
                double  seconds = Bass.BASS_ChannelBytes2Seconds( this._handle, length );
                this.Duration = TimeSpan.FromSeconds( seconds );
            }

            this.PlayState = PlayState.Stopped;
        }

        /// <summary>
        /// Bass.Net を解放します。
        /// オーディオ再生が実行されている場合は停止されます。
        /// このメソッドを呼び出した場合、再度 BassInitialize メソッドを呼び出すまで Bass.Net は利用できません。
        /// </summary>
        public static void BassFree()
        {
            if( !AudioPlayer.IsBassInitialized ) { return; }

            Bass.BASS_Stop();
            Bass.BASS_PluginFree( 0 );
            Bass.FreeMe();

            AudioPlayer.IsBassInitialized = false;
        }

        /// <summary>
        /// Bass.Net を初期化します。
        /// Bass.Net のライフタイムは、このメソッドから BassFree メソッドの呼び出しまでとなります。
        /// </summary>
        /// <param name="folderPath">BASS ライブラリのモジュールを格納したフォルダを示すパス文字列。</param>
        /// <exception cref="Exception">初期化に失敗した。</exception>
        public static void BassInitialize( string folderPath )
        {
            if( AudioPlayer.IsBassInitialized ) { return; }

            // Bass.Net のスプラッシュ スクリーンを抑止
            //BassNet.Registration( "mail-address", "register-code" );

            // Bass.Net
            if( !Bass.LoadMe( folderPath ) )
            {
                throw new Exception( "Bass.Net の初期化に失敗しました。" );
            }

            // デバイス
            if( !Bass.BASS_Init( -1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero ) )
            {
                var error = Bass.BASS_ErrorGetCode();
                throw new Exception( "デバイスの初期化に失敗しました。\nError : " + error.ToString() );
            }

            // プラグイン
            {
                var plugins = Bass.BASS_PluginLoadDirectory( folderPath );
                AudioPlayer.FileFilter = Utils.BASSAddOnGetPluginFileFilter( plugins, null );
            }

            AudioPlayer.IsBassInitialized = true;
        }

        /// <summary>
        /// ファイル ダイアログに指定する為のフィルタ文字列を取得します。
        /// </summary>
        public static string FileFilter { get; private set; }

        /// <summary>
        /// Bass.Net の初期化を終えている事を示す値を取得または設定します。
        /// </summary>
        private static bool IsBassInitialized { get; set; }

        #region IAudioPlayer メンバ

        /// <summary>
        /// 再生を一時停止します。
        /// </summary>
        public void Pause()
        {
            if( this.PlayState == PlayState.Playing )
            {
                this.PlayState = PlayState.Paused;
                Bass.BASS_ChannelPause( this._handle );
            }
        }

        /// <summary>
        /// 再生を開始します。
        /// </summary>
        public void Play()
        {
            if( this.PlayState != PlayState.Playing )
            {
                this.PlayState = PlayState.Playing;
                Bass.BASS_ChannelPlay( this._handle, false );
            }
        }

        /// <summary>
        /// 再生を停止します。
        /// </summary>
        public void Stop()
        {
            if( this.PlayState != PlayState.Stopped )
            {
                this.PlayState = PlayState.Stopped;
                Bass.BASS_ChannelStop( this._handle );
                Bass.BASS_ChannelSetPosition( this._handle, 0.0 );
            }
        }

        /// <summary>
        /// 再生位置の変更を行える事を示す値を取得します。
        /// </summary>
        public bool CanSeek { get { return true; } }

        /// <summary>
        /// 再生位置を時間単位で取得または設定します。
        /// </summary>
        public TimeSpan CurrentTime
        {
            get
            {
                var position = Bass.BASS_ChannelGetPosition( this._handle );
                return TimeSpan.FromSeconds( Bass.BASS_ChannelBytes2Seconds( this._handle, position ) );
            }
            set
            {
                var position = Bass.BASS_ChannelSeconds2Bytes( this._handle, value.TotalSeconds );
                Bass.BASS_ChannelSetPosition( this._handle, position );
            }
        }

        /// <summary>
        /// 演奏時間を取得します。
        /// </summary>
        public TimeSpan Duration { get; private set; }

        /// <summary>
        /// 音楽再生の状態を取得します。
        /// </summary>
        public PlayState PlayState { get; private set; }

        /// <summary>
        /// 音量を取得または設定します。
        /// </summary>
        public float Volume
        {
            get
            {
                return this._volume;
            }
            set
            {
                this._volume = value;
                Bass.BASS_ChannelSetAttribute( this._handle, BASSAttribute.BASS_ATTRIB_VOL, value );
            }
        }

        #endregion

        #region IDisposable メンバ

        /// <summary>
        /// リソースの解放を行います。
        /// </summary>
        public void Dispose()
        {
            if( this._handle != 0 )
            {
                this.Stop();
                Bass.BASS_StreamFree( this._handle );
                this._handle = 0;
            }
        }

        #endregion

        #region フィールド

        /// <summary>
        /// オーディオ再生を行う為のストリームのハンドル。
        /// </summary>
        private int _handle;

        /// <summary>
        /// 音量。
        /// </summary>
        private float _volume = 1.0f;

        #endregion
    }
}

BASS の API は Un4seen.Bass.Bass という静的クラスとして提供されているので、初期化と解放は一元化しておいた方が安全である。

その為、43 行目の BassFree と 60 行目の BassInitialize を静的メソッドとして定義し、プレイヤー クラスを使用する側からの呼び出しを義務づけた上で、複数回実行を回避するように実装している。

BASS を初期化するとスプラッシュ スクリーンが強制的に表示されるが、BassNet.Registration を呼び出す事で抑止できる。このメソッドには、BASS のユーザー登録が必要となる。BassNet.Registration の引数には、登録に使用したメールアドレスと、そのアドレスに送信されてきたメールにある登録コードを指定する。

尚、.NET アプリのアセンブリは .NET Reflector などを使用する事でソースコードを逆コンパイルできるので、BassNet.Registration を使用している部分から、メールアドレスと登録コードが見えてしまい、第三者に利用される可能性がある。

本稿のテーマから逸れるので詳しくは解説しないが、Visual Studio ( Express Edition は除く ) に付属している Dotfuscator Community Edition でも文字列リテラルの難読化を行えるので、心配なら実行しておく。

音楽ファイルを再生する場合は、Bass.BASS_StreamCreateFile でストリームを生成する。BASS 本体か Add-on が対応しているファイルなら、そのパスを指定する事でストリームが生成され、識別子として int 型のハンドルが返される。Bass.Net のメソッドではハンドルが必要となるので、フィールドとして記録しておこう。

Bass.Net にはサンプルとかなり詳細なヘルプが付属するので、今回のサンプル以上の機能 ( ASIO や WinAmp プラグインの利用など ) はそちらを参照の事。

世の中には多くのオーディオ プレイヤーがあり、それぞれが独自にプラグイン機能を実装しているが、BASS ぐらい高機能なライブラリが存在するなら、よほど特殊な事をしない限り、BASS を利用した方が資産を共有できて良いと思う。

また、好みのデザインやユーザビリティを持つプレイヤーが見つからないなら、オーディオ機能は BASS に任せて、自分専用のニッチなプレイヤーを作ってしまうのもアリだろう。

音量調整について

Bass.Net のオーディオ再生における音量調整は、システム全体、またはストリーム固有のボリュームが対象となる。システム全体のボリュームを選んだ場合は他のアプリケーションの音量にも影響するので、なるべくならストリーム固有を選んだ方が良い。

ストリームとシステムのボリュームを変更する処理は以下のようになる。

// ストリーム ( handle は Bass.BASS_StreamCreateFile の戻り値 )
Bass.BASS_ChannelSetAttribute( handle, BASSAttribute.BASS_ATTRIB_VOL, 1.0f );

// システム ( ボリュームを取得する場合は Bass.BASS_GetVolume )
Bass.BASS_SetVolume( 1.0f );

設定できる音量の範囲は 0 ( 無音 ) ~ 1 ( 最大 ) で、型は float となる。小数点以下を指定する事で、詳細な音量変更が行える。

Bass.BASS_ChannelSetAttribute は指定されたストリーム固有のパラメータを設定するメソッドで、音量の他にも定位や速度変更などが用意されており、ストリームがサポートしていれば様々な操作を行えるようになっている。