2006/02/27(月)

●オーディオプレイヤ開発記 その22

 聞々ハヤえもん 2.00 α 1 開発版 を公開しました。WAVE, MP3, Ogg Vorbis の読み込み、再生速度・再生周波数・音程の変更が可能です。Windows XP, Windows Me での動作は確認しましたが、他の環境での動作はテスト環境がないのでわかりません。動作報告は歓迎します。

 まだα版なので、足りない機能や動きのおかしな部分などがいくつか残っています。とりあえず、機能が Version 1 系統に追いつくまでは、α版・β版として出していきます。安定版を出すまでに修正していく予定の箇所は以下のとおり。

  • スライダを動かしてからファイルを読み込むと、再生速度などの変更が適用されていないのを修正する。
  • 巻き戻し・早送り機能をつける。
  • コンパイラが大量の警告を吐いているので、それを修正する。
  • プレイリスト部分をつくる。
  • コントロールバー・プレイリストの表示・非表示を切り替えられるようにする。
  • ファイルの保存ができるようにする。
  • ウィンドウ上へのファイルのドロップに対応する。
  • アイコンへのファイルのドロップに対応する。
  • メニューとツールバー、再生時間設定用スライダと再生速度表示用ラベルとの間に区切り線をつける。
  • そのほか、Version 1 系統にある機能は一通りつける。

 というわけで、ここらでいったんオーディオプレイヤ開発記は終了したいと思います。

2006/02/26(日)

●オーディオプレイヤ開発記 その21

 それでは、bass.dll を使った音声ファイルの再生を管理する CBass クラスを作ってみます。

 とりあえず、追加の依存ファイルに bass.lib を追加します。それで、bass.dll を使うアプリケーションでは、bass.h ファイルをインクルードする必要があるんですが、Bass.h という名前を CBass クラスのヘッダファイルに使いたいので、もともとの bass.h ファイルの名前を bassinc.h などに変更しておきます。

 それでは、CBass クラスを作成します。例によって、順次、必要な機能だけを追加していきます。

//----------------------------------------------------------------------------
// Bass.h : bass.dll の管理を行う
//----------------------------------------------------------------------------
#ifndef BassH
#define BassH

#include "bassinc.h"
//----------------------------------------------------------------------------
// bass.dll の管理を行うクラス
//----------------------------------------------------------------------------
class CBass
{
public: // 関数

    CBass(): m_hStream(0) { }
    virtual ~CBass() { BASS_Free(); }

    virtual QWORD ChannelGetLength() {
        return BASS_ChannelGetLength(m_hStream);
    }
    virtual QWORD ChannelGetPosition() {
        return BASS_ChannelGetPosition(m_hStream);
    }
    virtual float ChannelGetSecondsLength() {
        return BASS_ChannelBytes2Seconds(m_hStream, ChannelGetLength());
    }
    virtual float ChannelGetSecondsPosition() {
        return BASS_ChannelBytes2Seconds(m_hStream, ChannelGetPosition());
    }
    virtual BOOL ChannelPause() {
        return BASS_ChannelPause(m_hStream);
    }
    virtual BOOL ChannelPlay() {
        return BASS_ChannelPlay(m_hStream, FALSE);
    }
    virtual BOOL Init(HWND hWnd) {
        return BASS_Init(-1, 44100, 0, hWnd, NULL);
    }
    virtual BOOL Pause() {
        return BASS_Pause();
    }
    virtual BOOL Start() {
        return BASS_Start();
    }
    virtual BOOL StreamCreateFile(LPCTSTR lpFilePath) {
        StreamFree();
        m_hStream = BASS_StreamCreateFile(FALSE, lpFilePath, 0, 0, 0);
        return m_hStream ? TRUE : FALSE;
    }
    virtual void StreamFree() {
        if(m_hStream) BASS_StreamFree(m_hStream), m_hStream = 0;
    }

protected: // メンバ変数

    HSTREAM m_hStream;
};
//----------------------------------------------------------------------------

#endif

 完全にラップを被せてるだけですね。

 CMainWnd クラスに CBass m_bass; メンバ変数を追加し、OnCreate 関数内で m_bass.Init(m_hWnd); と初期化するようにしたら、次は ファイル → 開く メニューが選択された場合に「ファイルを開く」ダイアログを表示し、指定された音声ファイルを再生するようにします。

 メニューが選ばれると CMenu_MainWnd クラスの OnCommand 関数が呼ばれるので、そこから OnOpenFileMenuSelected 関数を呼ぶようにします。

//----------------------------------------------------------------------------
// メニューが選択された
//----------------------------------------------------------------------------
void CMenu_MainWnd::OnCommand(int id, HWND/* hwndCtl*/, UINT/* codeNotify*/)
{
    switch(id)
    {
    case ID_OPENFILE:
        OnOpenFileMenuSelected();
        break;
    case ID_XXXX:
        OnXxxxMenuSelected();
        break;
    }
}
//----------------------------------------------------------------------------
// ファイル → 開くメニューが選択された
//----------------------------------------------------------------------------
void CMenu_MainWnd::OnOpenFileMenuSelected()
{
    m_rMainWnd.OpenFile();
}
//----------------------------------------------------------------------------

 CMainWnd::OpenFile 関数の方も追加しておきます。

//----------------------------------------------------------------------------
// ファイルを開く
//----------------------------------------------------------------------------
void CMainWnd::OpenFile()
{
    TCHAR filePath[MAX_PATH] = "";
    OPENFILENAME ofn;
    ZeroMemory(&ofn, sizeof(OPENFILENAME));
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hInstance = GetModuleHandle(NULL);
    ofn.hwndOwner = m_hWnd;
    ofn.lpstrFilter = TEXT("再生可能な音声ファイル(*.wav;*.mp3;*.ogg)\0*.wav;*.mp3;*.ogg\0すべてのファイル (*.*)\0*.*\0\0");
    ofn.lpstrFile = filePath;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_FILEMUSTEXIST;

    if(GetOpenFileName(&ofn))
    {
        m_bass.StreamCreateFile(filePath);
        m_bass.ChannelPlay();
        m_timeLabel.SetTime((int)m_bass.ChannelGetSecondsPosition(), (int)m_bass.ChannelGetSecondsLength());
        m_timeSlider.SetTime((LONG)m_bass.ChannelGetPosition(), (LONG)m_bass.ChannelGetLength());
    }
}
//----------------------------------------------------------------------------

 これで、指定された音声ファイルが再生されるようになりました。ここまでのソースコードはこちら ( Hayaemon20060226_src.lzh ) 。

 次は、ファイルの再生位置にあわせて、再生位置表示用ラベルやスライダを変更するようにします。これは、0.5 秒間隔のタイマーでやることにします。まず、CWnd クラスに SetTimer, KillTimer, OnTimer 関数を追加し、WndProc から OnTimer に分岐するようにします。

virtual void KillTimer(UINT_PTR nIDEvent) {
    ::KillTimer(m_hWnd, nIDEvent);
}
virtual void SetTimer(UINT_PTR nIDEvent, UINT nElapse) {
    ::SetTimer(m_hWnd, nIDEvent, nElapse, NULL);
}
virtual void OnTimer(UINT id) {
    WindowProc(WM_TIMER, (WPARAM)id, 0);
}
// CWnd::WndProc 関数内
case WM_TIMER:
    pWnd->OnTimer((UINT)wParam);
    return 0;

 それで、CWnd::OnTimer 関数をオーバーライドした CMainWnd::OnTimer 関数を追加し、ファイルが開かれた時点で、SetTimer(1, 500); とするようにしておきます。

virtual void OnTimer(UINT id) {
    m_timeLabel.SetTime(m_bass.ChannelGetSecondsPosition(), m_bass.ChannelGetSecondsLength());
    m_timeSlider.SetThumbPos(m_bass.ChannelGetPosition());
    CFrameWnd::OnTimer(id);
}

 これで、ファイルの再生位置にあわせて、再生位置表示用ラベルやスライダが変更されるようになりました。ここまでのソースコードはこちら ( Hayaemon20060226_2_src.lzh ) 。

 続いて、ツールバーの「ファイルを開く」ボタンが押された場合にも、「ファイルを開く」ダイアログを出すようにします。まず、CMainWnd::OnCommand 関数からメニューやツールバーの OnCommand 関数を呼び出すようにします。

virtual void OnCommand(int id, HWND hwndCtl, UINT codeNotify) {
    if(!hwndCtl)
        m_menu.OnCommand(id, hwndCtl, codeNotify);
    else if(hwndCtl == (HWND)m_toolBar)
        m_toolBar.OnCommand(id, hwndCtl, codeNotify);
    CFrameWnd::OnCommand(id, hwndCtl, codeNotify);
}

 CToolBar_MainWnd クラスに OnCommand, OnOpenFileButtonSelected 関数を追加しておきます。

//----------------------------------------------------------------------------
// ファイルを開くボタンが選択された
//----------------------------------------------------------------------------
void CToolBar_MainWnd::OnOpenFileButtonSelected()
{
    m_rMainWnd.OpenFile();
}
//----------------------------------------------------------------------------
// ボタンが選択された
//----------------------------------------------------------------------------
void CToolBar_MainWnd::OnCommand(int id, HWND/* hwndCtl*/, UINT/* codeNotify*/)
{
    switch(id)
    {
    case ID_OPENFILE:
        OnOpenFileButtonSelected();
        break;
    }
}
//----------------------------------------------------------------------------

 これで、ツールバーの「ファイルを開く」ボタンが押された場合にも、「ファイルを開く」ダイアログが出るようになりました。ここまでのソースコードはこちら ( Hayaemon20060226_3_src.lzh ) 。

2006/02/25(土)

●オーディオプレイヤ開発記 その20

 bass.dll について色々と調べていたところ、タイムストレッチとピッチシフトを実現するアドオンが見つかりました。えーとこれを使えば、再生速度の変換も音程の変換もできて、WAVE も MP3 も Ogg Vorbis も再生できて……、と。うん、音まわりはほとんどやることがないんじゃないですか。

 とりあえず、「安定して使えれば」、「音質が良ければ」という条件がつくので、bass.dll を使ったファイルの再生ができ、再生速度・再生周波数・音程がスライダで変更できるようになったら、アルファ版として出してみようかと思います。

 本当に bass.dll を使うとなると、内部仕様を大きく変更することになるので、たぶん聞々ハヤえもん Version 2 系統として出していくことになると思います。もしそうなった場合、現行の Version 1 系統はあれで完成ということになります。うん、なんていうか、おめでとう。もしそうなったらだけど。

2006/02/24(金)

●オーディオプレイヤ開発記 その19

 そろそろ音まわりに入っていきます。プレイリスト用のリストビューコントロールや速度・周波数の変更用スライダなどは、1つのファイルがきちんと再生できるようになってから対応します。

 で、音まわりに何を使うのかというのを考える必要があり、色々と調べなおしています。エフェクトをかけたいのと、MP3, Ogg Vorbis など様々なファイルフォーマットに対応したいという条件により、MCI や DirectMusic は選択肢から外れます。で、DirectSound でやるか、WaveOut 系の API でやるか、OpenAL でやるかというあたりが普通のようです。まあベストは、全部対応しておいて設定画面で切り替えられるようにするってのですね。ちなみに、MP3 などのデコードには、ACM を使う、フリーのライブラリを使う、自前でデコードするなどの選択肢がありえますが、たぶん ACM を使うことになると思います。

 ただちょっと気になるのが、bass.dll ( http://www.un4seen.com/ ) という高性能オーディオライブラリです。WAVE や MP3, Ogg Vorbis ほか様々なフォーマットに対応しており、ストリーム再生ができるらしいんですが、フリーソフト内で使う場合であれば無料で使えるようです。これが安定して使えるとプログラミングの手間がだいぶ省けることになるので、ちょっと色々と試してみるつもりです。

2006/02/23(木)

●オーディオプレイヤ開発記 その18

 今回は、前回作成した CSliderCtrl クラスを継承して、再生時間の設定用に CTimeSlider_MainWnd クラスを作ります。スライダの縦位置をツールバー、再生時間表示用ラベルより下にする必要があるので、CTimeSlider_MainWnd クラスからツールバー、再生時間表示用ラベルの高さを得られるようにします。そのために、CMainWnd クラスに GetToolBar, GetTimeLabel 関数を追加しておきます。

const CToolBar_MainWnd & GetToolBar() const { return m_toolBar; }
const CTimeLabel_MainWnd & GetTimeLabel() const { return m_timeLabel; }

 それでは、CTimeSlider_MainWnd クラスを作成します。

//----------------------------------------------------------------------------
// TimeSlider_MainWnd.h : 再生時間設定用スライダの管理を行う
//----------------------------------------------------------------------------
#ifndef TimeSlider_MainWndH
#define TimeSlider_MainWndH

class CApp;
class CMainWnd;
#include "../Common/SliderCtrl.h"
//----------------------------------------------------------------------------
// 再生時間設定用スライダの管理を行うクラス
//----------------------------------------------------------------------------
class CTimeSlider_MainWnd : public CSliderCtrl
{
public: // 関数

    CTimeSlider_MainWnd(CApp & app, CMainWnd & mainWnd)
        : m_rApp(app), m_rMainWnd(mainWnd) { }
    virtual ~CTimeSlider_MainWnd() { }

    virtual BOOL Create();
    virtual void ResetSize();
    virtual void SetTime(LONG time, LONG totalTime);

private: // メンバ変数

    CApp & m_rApp;
    CMainWnd & m_rMainWnd;
};
//----------------------------------------------------------------------------

#endif
//----------------------------------------------------------------------------
// TimeSlider_MainWnd.cpp : 再生時間表示用ラベルの管理を行う
//----------------------------------------------------------------------------
#include <windows.h>
#include "../App.h"
#include "MainWnd.h"
#include "TimeSlider_MainWnd.h"
//----------------------------------------------------------------------------
// 作成
//----------------------------------------------------------------------------
BOOL CTimeSlider_MainWnd::Create()
{
    CSliderCtrl::Create(m_rMainWnd);
    if(!m_hWnd) return FALSE;

    SetStyle(GetStyle() | WS_TABSTOP | TBS_AUTOTICKS | TBS_HORZ | TBS_NOTICKS);
    int nToolBarHeight = m_rMainWnd.GetToolBar().GetHeight();
    int nTimeLabelHeight = m_rMainWnd.GetTimeLabel().GetHeight();
    int nBigger = nToolBarHeight > nTimeLabelHeight ? nToolBarHeight : nTimeLabelHeight;
    SetPos(0, nBigger);
    ResetSize();

    Show(SW_SHOW);
    return TRUE;
}
//----------------------------------------------------------------------------
// サイズの再設定
//----------------------------------------------------------------------------
void CTimeSlider_MainWnd::ResetSize()
{
    SetSize(m_rMainWnd.GetClientWidth(), GetSystemMetrics(SM_CYHSCROLL) * 1.5);
}
//----------------------------------------------------------------------------
// スライダをバイト単位で設定
//----------------------------------------------------------------------------
void CTimeSlider_MainWnd::SetTime(LONG bytes, LONG totalBytes)
{
    SetRange(0, totalBytes);
    SetThumbPos(bytes);
}
//----------------------------------------------------------------------------

 とりあえずスライダの縦のサイズは、画面のプロパティで設定するスクロールバーの高さ×1.5倍にしておきましたけど、これってそんなアバウトでいいんでしょうか。どのぐらいにするのが普通なんでしょう。

 それでは、この TimeSlider_MainWnd.h を MainWnd.h からインクルードしておき、CMainWnd クラスのメンバ変数に CTimeSlider_MainWnd m_timeSlider; を持つようにし、コンストラクタで初期化を行い、CMainWnd::Create で再生時間表示用ラベルを作成するようにします。

CMainWnd(CApp & app): m_rApp(app), m_menu(app, *this), m_toolBar(app, *this),
    m_timeLabel(app, *this), m_timeSlider(app, *this) { }
// 再生時間設定用スライダの作成
if(!m_timeSlider.Create())
{
    MessageBox(m_hWnd, TEXT("再生時間設定用スライダの作成に失敗しました。"
                "アプリケーションを終了します。"), TEXT("エラー"), MB_ICONERROR);
    Destroy();
    return FALSE;
}

 これで、再生時間設定用スライダが表示されるようになりました。ついでに、CMainWnd::OnSize 関数に、m_timeSlider.ResetSize(); を追加し、ウィンドウのサイズが変更された際にスライダのサイズを再設定するようにしておきます。ここまでのソースコードはこちら ( Hayaemon20060223_src.lzh ) 。

2006/02/22(水)

●サウンドゲームシリーズ「ブロック崩し」 1.01 公開

 サウンドゲームシリーズ「ブロック崩し」 1.01 を公開しました。

 音楽や効果音を一新したんですけど、ブロックを消すときの音がかなり気持ちいいです。

2006/02/21(火)

●オーディオプレイヤ開発記 その17

 今回は、スライダを管理するクラス CSliderCtrl です。オーディオプレイヤでは、再生時間の設定や速度・周波数の設定などに使えます。

//----------------------------------------------------------------------------
// SliderCtrl.h : スライダコントロールの管理を行う
//----------------------------------------------------------------------------
#ifndef SliderCtrlH
#define SliderCtrlH

#include <commctrl.h>
#include "Wnd.h"
//----------------------------------------------------------------------------
// スライダコントロールの管理を行うクラス
//----------------------------------------------------------------------------
class CSliderCtrl : public CWnd
{
public: // 関数

    CSliderCtrl() { }
    virtual ~CSliderCtrl() { }

    virtual BOOL Create(HWND hParentWnd)
    {
        Destroy();

        INITCOMMONCONTROLSEX ic;
        ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
        ic.dwICC = ICC_BAR_CLASSES;
        InitCommonControlsEx(&ic);

        m_hWnd = CreateWindow(TRACKBAR_CLASS, "", WS_CHILD, CW_USEDEFAULT,
                                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                                hParentWnd, 0, GetModuleHandle(NULL), NULL);
        if(!m_hWnd) return FALSE;
        SetProc();
        return TRUE;
    }
    virtual LRESULT SetLineSize(LONG lLineSize)
    {
        return SendMessage(m_hWnd, TBM_SETLINESIZE, 0, (LPARAM)lLineSize);
    }
    virtual int SetPageSize(LONG lPageSize)
    {
        return SendMessage(m_hWnd, TBM_SETPAGESIZE, 0, (LPARAM)lPageSize);
    }
    virtual void SetRange(LONG lMin, LONG lMax, BOOL bRedraw = FALSE)
    {
        SendMessage(m_hWnd, TBM_SETRANGE, (WPARAM)bRedraw, MAKELPARAM(lMin, lMax));
    }
    virtual void SetThumbPos(LONG lPos, BOOL bRedraw = TRUE)
    {
        SendMessage(m_hWnd, TBM_SETPOS, (WPARAM)bRedraw, lPos);
    }
};
//----------------------------------------------------------------------------

#endif

 次回は、再生時間の設定用にこのクラスを継承した CTimeSlider_MainWnd クラスを作る予定です。

2006/02/20(月)

●吉里吉里/KAG 用フェードのあるスタッフロールを作るための KAG プラグイン

 メールの方でフェードのあるスタッフロールの話題が出たので、1からきちんと作り直してプラグイン化してみました。

 ダウンロード ( fadestaffroll20060220.lzh )

; fadestaffroll.ks ---------------------
@if exp="typeof(global.fadestaffroll_object) == 'undefined'"
@iscript

// フェードのあるスタッフロールのプラグイン

// KAG に付属のスタッフロールプラグインとは違い、
// メッセージレイヤを移動させるタイプのスタッフロール

class FadeStaffRollPlugin extends KAGPlugin
{
    var layer; // フェードっぽく見せるためのレイヤ
    var window; // ウィンドウオブジェクトへの参照

    function FadeStaffRollPlugin(window)
    {
        this.window = window;
        layer = new KAGLayer(window, window.fore.base);
        layer.absolute = 2000000-1; // 重ね合わせ順序はメッセージ履歴よりも奥
        layer.hitType = htMask;
        layer.hitThreshold = 256; // マウスメッセージは全域透過
        layer.setPos(0, 0, window.scWidth, window.scHeight);
        super.KAGPlugin();
    }

    function finalize()
    {
        invalidate layer;
        super.finalize(...);
    }

    function init(left, top, right, bottom, vertical, accel)
    {
        // left, top, right, bottom : スタッフロールの表示範囲
        // vertical : 縦書きかどうか
        // accel : 加速度的な動きを行うか ( 負 : 0 : 正 )

        // 画面をいったん layer に重ね合わせコピー
        var base = window.fore.base;
        layer.piledCopy(0, 0, base, 0, 0, layer.width, layer.height);
        layer.face = dfMask;
        layer.fillRect(0, 0, layer.width, layer.height, 255);

        // フェードの範囲内を見えるようにする
        layer.fillRect(left, top, right - left, bottom - top, 0);

        // フェード部分
        if(vertical)
        {
            // 左側のフェード
            var margin = (right - left) / 2;
            var h = bottom - top;
            for(var i = 0; i < margin; i++)
            {
                var mask = 1 - i / margin;
                mask = calculateMask(mask, accel);
                layer.fillRect(left + i, top, 1, h, mask);
            }

            // 右側のフェード
            var r = right - margin;
            for(var i = 0; i < margin; i++)
            {
                var mask = i / margin;
                mask = calculateMask(mask, accel);                 layer.fillRect(r + i, top, 1, h, mask);
            }
        }
        else
        {
            // 上側のフェード
            var margin = (bottom - top) / 2;
            var w = right - left;
            for(var i = 0; i < margin; i++)
            {
                var mask = 1 - i / margin;
                mask = calculateMask(mask, accel);
                layer.fillRect(left, top + i, w, 1, mask);
            }

            // 下側のフェード
            var b = bottom - margin;
            for(var i = 0; i < margin; i++)
            {
                var mask = i / margin;
                mask = calculateMask(mask, accel);
                layer.fillRect(left, b + i, w, 1, mask);
            }
        }

        layer.visible = true;
    }

    function calculateMask(mask, accel)
    {
        // 加速計算
        if(accel < 0)
        {
            // 上弦 ( 最初が動きが早く、徐々に遅くなる )
            mask = 1.0 - mask;
            mask = Math.pow(mask, -accel);
            mask = 1.0 - mask;
        }
        else if(accel > 0)
        {
            // 下弦 ( 最初は動きが遅く、徐々に早くなる )
            mask = Math.pow(mask, accel);
        }
        return 255 * mask;
    }

    function uninit()
    {
        layer.visible = false;
    }

    function onRestore(f, clear, elm)
    {
        // 栞を読み出すとき
        uninit();
    }
}

kag.addPlugin(global.fadestaffroll_object = new FadeStaffRollPlugin(kag));
    // プラグインオブジェクトを作成し、登録する

@endscript
@endif

@macro name=fadestaffrollinit
@eval exp="fadestaffroll_object.init(+mp.left, +mp.top, +mp.right, +mp.bottom, +mp.vertical, +mp.accel)"
@endmacro

@macro name=fadestaffrolluninit
@eval exp="fadestaffroll_object.uninit()"
@endmacro
@return
;---------------------------------------

 シナリオの先頭などで

@call storage="fadestaffroll.ks"

として fadestaffroll.ks を呼び出すと fadestaffrollinit と fadestaffrolluninitという2つのタグが定義されます。

・fadestaffrollinit

 プラグインを初期化します。

 left, top, right, bottom 属性 : フェードアウトを表示する範囲
 vertical 属性                 : 縦書きかどうか
 accel 属性                    : 加速度的な動きを行うか ( 負 : 0 : 正 )

・fadestaffrolluninit

 プラグインの使用を中止します。

 シナリオ内での使い方などは、first.ks ファイルがサンプルになっていますので、そちらをご覧ください。

; first.ks -----------------------------
@call storage=fadestaffroll.ks

@image layer=base storage=bgimage rgamma=0.2 ggamma=0.2 bgamma=0.2
@nowait
@defstyle linespacing=100
@deffont face="MS ゴシック" size=32 color=0xf0f0f0 edge=true edgecolor=0x404040 shadow=false
@position opacity=0 top=&kag.scHeight-kag.current.defaultLineSpacing height="&kag.current.marginT + kag.current.marginB + (kag.current.defaultFontSize + kag.current.defaultLineSpacing) * 13"
@style align=right
スタッフ1[r]
スタッフ2[r]
スタッフ3[r]
スタッフ4[r]
スタッフ5[r]
スタッフ6[r]
スタッフ7[r]
スタッフ8[r]
スタッフ9[r]
スタッフ10[r]
[r]
[r]
Produced by スタッフ
@fadestaffrollinit left=0 top=60 right=&kag.scWidth bottom=&kag.scHeight-60 vertical=false accel=10
@move layer=message time=20000 path="&(kag.current.left+ ', ' + (-kag.current.height + kag.scHeight / 2 + kag.current.marginB + kag.current.defaultFontSize / 2) + ', 255')"
@wm clickskip=true
@fadestaffrolluninit
;---------------------------------------

2006/02/19(日)

●オーディオプレイヤ開発記 その16

 今回は、再生時間表示用ラベルを管理する CTimeLabel_MainWnd クラスを作成します。

 まずは、CWnd クラスに GetWidth, GetClientWidth 関数を追加しておきます。

virtual int GetClientWidth() const {
    RECT rc;
    GetClientRect(m_hWnd, &rc);
    return rc.right - rc.left;
}
virtual int GetWidth() const {
    RECT rc;
    GetWindowRect(m_hWnd, &rc);
    return rc.right - rc.left;
}

 それでは、CStatic クラスを継承した CTimeLabel_MainWnd クラスを作ります。

//----------------------------------------------------------------------------
// TimeLabel_MainWnd.h : 再生時間表示用ラベルの管理を行う
//----------------------------------------------------------------------------
#ifndef TimeLabel_MainWndH
#define TimeLabel_MainWndH

class CApp;
class CMainWnd;
#include "../Common/Static.h"
#include "../Common/Font.h"
//----------------------------------------------------------------------------
// 再生時間表示用ラベルの管理を行うクラス
//----------------------------------------------------------------------------
class CTimeLabel_MainWnd : public CStatic
{
public: // 関数

    CTimeLabel_MainWnd(CApp & app, CMainWnd & mainWnd)
        : m_rApp(app), m_rMainWnd(mainWnd) { }
    virtual ~CTimeLabel_MainWnd() { }

    virtual BOOL Create();
    virtual void ResetPos();
    virtual void SetTime(int time, int totalTime);

private: // メンバ変数

    CApp & m_rApp;
    CMainWnd & m_rMainWnd;

    CFont m_font;
};
//----------------------------------------------------------------------------

#endif
//----------------------------------------------------------------------------
// TimeLabel_MainWnd.cpp : 再生時間表示用ラベルの管理を行うクラス
//----------------------------------------------------------------------------
#include <windows.h>
#include "../App.h"
#include "MainWnd.h"
#include "TimeLabel_MainWnd.h"
//----------------------------------------------------------------------------
// 作成
//----------------------------------------------------------------------------
BOOL CTimeLabel_MainWnd::Create()
{
    CStatic::Create();
    if(!m_hWnd) return FALSE;
    SetParent(m_rMainWnd);

    // 画面のプロパティで設定されているタイトルバーと同じシステムフォントに設定
    NONCLIENTMETRICS ncm;
    ZeroMemory(&ncm, sizeof(NONCLIENTMETRICS));
    ncm.cbSize = sizeof(NONCLIENTMETRICS);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
    m_font.CreateIndirect(&ncm.lfCaptionFont);
    SetFont(m_font, TRUE);

    SetTime(0, 0);

    Show(SW_SHOW);
    return TRUE;
}
//----------------------------------------------------------------------------
// 位置の再設定
//----------------------------------------------------------------------------
void CTimeLabel_MainWnd::ResetPos()
{
    // ウィンドウの右端に設定
    SetPos(m_rMainWnd.GetClientWidth() - GetWidth(), 2);
}
//----------------------------------------------------------------------------
// 表示する時間を秒単位で設定
//----------------------------------------------------------------------------
void CTimeLabel_MainWnd::SetTime(int time, int totalTime)
{
    int hour = (time / 3600) % 60;
    int second = (time / 60) % 60;
    int minute = time % 60;

    int totalHour = (totalTime / 3600) % 60;
    int totalSecond = (totalTime / 60) % 60;
    int totalMinute = totalTime % 60;

    TCHAR text[20];
    if(totalHour > 0)
        wsprintf(text, "%02d:%02d:%02d / %02d:%02d:%02d", hour, second, minute, totalHour, totalSecond, totalMinute);
    else
        wsprintf(text, "%02d:%02d / %02d:%02d", second, minute, totalSecond, totalMinute);
    SetText(text);
    SetSizeToTextSize();
    ResetPos();
}
//----------------------------------------------------------------------------

 この TimeLabel_MainWnd.h を MainWnd.h からインクルードしておき、CMainWnd クラスのメンバ変数に CTimeLabel_MainWnd m_timeLabel; を持つようにし、コンストラクタで初期化を行い、CMainWnd::Create で再生時間表示用ラベルを作成するようにします。

CMainWnd(CApp & app): m_rApp(app), m_menu(app, *this), m_toolBar(app, *this),
    m_timeLabel(app, *this) { }
// 再生時間表示用ラベルの作成
if(!m_timeLabel.Create())
{
    MessageBox(m_hWnd, TEXT("再生時間表示用ラベルの作成に失敗しました。"
                "アプリケーションを終了します。"), TEXT("エラー"), MB_ICONERROR);
    Destroy();
    return FALSE;
}

 これで、再生時間表示用ラベルが表示されるようになりました。ついでに、CMainWnd::OnSize 関数に、m_timeLabel.ResetPos(); を追加し、ウィンドウのサイズが変更された際にラベルの位置を再設定するようにしておきます。

 ラベルに表示される文字のフォントについては、とりあえず、画面のプロパティで設定されているタイトルバーと同じシステムフォントに設定してますが、いずれ、右クリックメニューなどからカスタマイズできるようにしたいです。ここまでのソースコードはこちら ( Hayaemon20060219_src.lzh ) 。

2006/02/17(金)

●オーディオプレイヤ開発記 その15

 今回は、フォントを管理する CFont クラスを作成します。これによって例えば、ラベルなどに表示される文字のフォントを設定できるようにします。

 それでは、早速 CFont クラスを作成します。

//----------------------------------------------------------------------------
// Font.h : フォントの作成・管理・破棄を行う
//----------------------------------------------------------------------------
#ifndef FontH
#define FontH
//----------------------------------------------------------------------------
// フォントの作成・管理・破棄を行うクラス
//----------------------------------------------------------------------------
class CFont
{
public: // 関数

    CFont(): m_hFont(0) { }
    virtual ~CFont() { Destroy(); }

    virtual BOOL Create(int nHeight, int nWidth, int nEscapement,
                        int nOrientation, int nWeight, BYTE bItalic,
                        BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet,
                        BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality,
                        BYTE nPitchAndFamily, LPCTSTR lpszFacename)
    {
        m_hFont = CreateFont(nHeight, nWidth, nEscapement, nOrientation,
                                nWeight, bItalic, bUnderline, cStrikeOut,
                                nCharSet, nOutPrecision, nClipPrecision,
                                nQuality, nPitchAndFamily, lpszFacename);
        return (m_hFont ? TRUE : FALSE);
    }
    virtual BOOL CreateIndirect(const LOGFONT* pLogFont) {
        m_hFont = CreateFontIndirect(pLogFont);
        return (m_hFont ? TRUE : FALSE);
    }
    virtual void Destroy() {
        if(m_hFont) DeleteObject(m_hFont), m_hFont = 0;
    }

protected: // メンバ変数

    HFONT m_hFont;

public: // メンバ変数の取得・設定

    operator HFONT() const { return m_hFont; }
};
//----------------------------------------------------------------------------

#endif

 これを、コントロールなどに対して適用できるようにするため、CWnd クラスに SetFont 関数を追加します。

virtual void SetFont(HFONT hFont, BOOL fRedraw = TRUE) {
    SendMessage(m_hWnd, WM_SETFONT, (WPARAM)hFont, (LPARAM)fRedraw);
}

 これで、フォントを設定できるようになりました。

 ところでさっき気づいたんですけど、CStatic クラスの SetSizeToTextSize 関数がデフォルトのシステムフォントにしか対応していませんでしたので、これを修正します。ついでに必要となる関数もいくつか追加しておきます。

 まず、CWnd::GetFont 関数を追加します。

virtual HFONT GetFont() const {
    return (HFONT)SendMessage(m_hWnd, WM_GETFONT, 0, 0);
}

 次に、CDC::SelectFont 関数を追加します。

virtual HFONT SelectFont(HFONT hFont) {
    return (HFONT)SelectObject(m_hDC, hFont);
}

 そして、CStatic::GetTextHeight, GetTextWidth 関数を修正します。

virtual int GetTextHeight() const {
    if(!m_hWnd) return -1;

    CClientDC dc(m_hWnd);
    HFONT hFont = GetFont();
    HFONT hOrgFont = 0;
    if(hFont)
        hOrgFont = dc.SelectFont(hFont);
    int height = dc.GetTextHeight(GetText());
    if(hOrgFont)
        dc.SelectFont(hOrgFont);
    return height;
}
virtual int GetTextWidth() const {
    if(!m_hWnd) return -1;

    CClientDC dc(m_hWnd);
    HFONT hFont = GetFont();
    HFONT hOrgFont = 0;
    if(hFont)
        hOrgFont = dc.SelectFont(hFont);
    int width = dc.GetTextWidth(GetText());
    if(hOrgFont)
        dc.SelectFont(hOrgFont);
    return width;
}

 これで、フォントを変更した場合でも、SetSizeToTextSize 関数がきちんと動作するようになりました。

 次は、再生時間表示用ラベルを管理する CTimeLabel_MainWnd クラスを作成する予定です。