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/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/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 クラスを作成する予定です。

2006/02/16(木)

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

 今回は、ツールバー上に一定時間カーソルがあわせられたときに、ツールチップを出すようにします。ツールチップが必要になった場合、ウィンドウプロシージャには WM_NOTIFY というメッセージが飛んできます。またこのとき、lParam によって指定された、コモンコントロールからの通知メッセージを表す NMHDR 構造体の code 変数には、TTN_NEEDTEXT が入っています。そして、hwndFrom にコントロールのウィンドウハンドル、idFrom にコントロール ID が入っています。

 それではまず、CWnd クラスに、OnNotify 関数

virtual LRESULT OnNotify(int idFrom, NMHDR* pnmhdr) {
    return WindowProc(WM_NOTIFY, (WPARAM)idFrom, (LPARAM)pnmhdr);
}

を追加し、CWnd::WndProc の、

case WM_CREATE:
    return pWnd->OnCreate((LPCREATESTRUCT)lParam);

となっているあたりに、WM_NOTIFY の分岐を追加します。

case WM_CREATE:
    return pWnd->OnCreate((LPCREATESTRUCT)lParam);
case WM_NOTIFY:
    return pWnd->OnNotify((int)wParam, (NMHDR*)lParam);

 そして、CToolBar_MainWnd に、OnNotify, OnNeedText 関数を追加します。

// コモンコントロールからのメッセージ
virtual LRESULT OnNotify(int idFrom, NMHDR* pnmhdr)
{
    switch(pnmhdr->code)
    {
    case TTN_NEEDTEXT:
        OnNeedText(idFrom, (TOOLTIPTEXT*)pnmhdr);
        break;
    }
    return CToolBar::OnNotify(idFrom, pnmhdr);
}

// ツールチップが必要
virtual void OnNeedText(int idFrom, TOOLTIPTEXT* pttt)
{
    switch(idFrom)
    {
    case ID_OPENFILE:
        pttt->lpszText = TEXT("ファイルを開く");
        break;
    case ID_HEAD:
        pttt->lpszText = TEXT("巻き戻し\n頭出し/前へ");
        break;
    case ID_PLAY:
        pttt->lpszText = TEXT("再生");
        break;
    case ID_PAUSE:
        pttt->lpszText = TEXT("一時停止");
        break;
    case ID_STOP:
        pttt->lpszText = TEXT("停止");
        break;
    case ID_NEXT:
        pttt->lpszText = TEXT("早送り\n次へ");
        break;
    case ID_SLOOP:
        pttt->lpszText = TEXT("1曲ループ");
        break;
    case ID_ALOOP:
        pttt->lpszText = TEXT("全曲ループ");
        break;
    case ID_RANDOM:
        pttt->lpszText = TEXT("ランダム再生 ( 重複あり )");
        break;
    case ID_ABLOOP_A:
        pttt->lpszText = TEXT("AB ループ ( A )");
        break;
    case ID_ABLOOP_B:
        pttt->lpszText = TEXT("AB ループ ( B )");
        break;
    case ID_CONTROLBAR:
        pttt->lpszText = TEXT("コントロールバーの表示状態");
        break;
    case ID_LIST:
        pttt->lpszText = TEXT("プレイリストの表示状態");
        break;
    }
}

 これで、ツールバーにツールチップが表示されるようになりました。

2006/02/15(水)

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

 今回から、ツールバーを作成・管理・破棄する CToolBar クラスを作っていきます。ツールバーにはビットマップを表示させるので、ついでに、ビットマップを管理する CBitmap クラスも作っておきます。

 それではまずは、CBitmap クラスです。

//----------------------------------------------------------------------------
// Bitmap.h : ビットマップの管理を行う
//----------------------------------------------------------------------------
#ifndef BitmapH
#define BitmapH
//----------------------------------------------------------------------------
// ビットマップの管理を行うクラス
//----------------------------------------------------------------------------
class CBitmap
{
public: // 関数

    CBitmap(): m_hBitmap(0) { }
    virtual ~CBitmap() { Destroy(); }

    virtual void Destroy() {
        if(m_hBitmap) DeleteObject(m_hBitmap), m_hBitmap = 0;
    }
    virtual void Load(LPCTSTR lpszName, UINT fuLoad) {
        Destroy();
        m_hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), lpszName, IMAGE_BITMAP, 0, 0, fuLoad);
    }

protected: // メンバ変数

    HBITMAP m_hBitmap;

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

    operator HBITMAP() const { return m_hBitmap; }
};
//----------------------------------------------------------------------------

#endif

 続いて、CToolBar クラスです。

//----------------------------------------------------------------------------
// ToolBar.h :ツールバーの作成・管理・破棄を行う
//----------------------------------------------------------------------------
#ifndef ToolBarH
#define ToolBarH

#include "Wnd.h"
#include "Bitmap.h"
//----------------------------------------------------------------------------
// ツールバーの作成・管理・破棄を行うクラス
//----------------------------------------------------------------------------
class CToolBar : public CWnd
{
public: // 関数

    CToolBar() { }
    virtual ~CToolBar() { }

    virtual BOOL Create(HWND hParentWnd);
    virtual void LoadBitmap(LPCTSTR lpszResourceName);
    virtual void SetButtons(const UINT* lpIDArray, int nIDCount);

protected: // メンバ変数

    CBitmap m_bitmap;
};
//----------------------------------------------------------------------------

#endif
//----------------------------------------------------------------------------
// ToolBar.cpp : ツールバーの作成・管理・破棄を行う
//----------------------------------------------------------------------------
#include <windows.h>
#include <commctrl.h>
#include "ToolBar.h"
//----------------------------------------------------------------------------
// 作成
//----------------------------------------------------------------------------
BOOL CToolBar::Create(HWND hParentWnd)
{
    INITCOMMONCONTROLSEX icc;
    ZeroMemory(&icc, sizeof(INITCOMMONCONTROLSEX));
    icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icc.dwICC = ICC_BAR_CLASSES;
    InitCommonControlsEx(&icc);

    m_hWnd = CreateWindow(TOOLBARCLASSNAME, "", WS_CHILD, CW_USEDEFAULT,
                            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                            hParentWnd, 0, GetModuleHandle(NULL), NULL);
    if(!m_hWnd) return FALSE;
    SetProc();
    return TRUE;
}
//----------------------------------------------------------------------------
// ビットマップの読み込み
//----------------------------------------------------------------------------
void CToolBar::LoadBitmap(LPCTSTR lpszResourceName)
{
    m_bitmap.Load(lpszResourceName, LR_LOADMAP3DCOLORS);

    SendMessage(m_hWnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);

    BITMAP bitmap;
    GetObject((HBITMAP)m_bitmap, sizeof(BITMAP), &bitmap);
    TBADDBITMAP tbab;
    ZeroMemory(&tbab, sizeof(TBADDBITMAP));
    tbab.nID = (UINT_PTR)(HBITMAP)m_bitmap;
    SendMessage(m_hWnd, TB_ADDBITMAP, bitmap.bmWidth / bitmap.bmHeight,
                (LPARAM)&tbab);
}
//----------------------------------------------------------------------------
// ボタンの設定
//----------------------------------------------------------------------------
void CToolBar::SetButtons(const UINT* lpIDArray, int nIDCount)
{
    if(nIDCount < 1) return; // 1個未満の場合はそのまま返す
    SendMessage(m_hWnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);

    // ボタンの追加
    TBBUTTON btn;
    ZeroMemory(&btn, sizeof(TBBUTTON));
    btn.fsState = TBSTATE_ENABLED;
    btn.fsStyle = TBSTYLE_BUTTON;
    if(lpIDArray) // 普通のボタンか区切り線
    {
        // ボタンを順次追加していく
        for(int i = 0, iBitmap = 0; i < nIDCount; i++, lpIDArray++)
        {
            btn.idCommand = *lpIDArray;
            if(btn.idCommand == 0) // 区切り線
                btn.fsStyle = TBSTYLE_SEP;
            else // 普通のボタン
            {
                btn.fsStyle = TBSTYLE_BUTTON;
                btn.iBitmap = iBitmap;
                iBitmap++;
            }
            SendMessage(m_hWnd, TB_ADDBUTTONS, 1, (LPARAM)&btn);
        }
    }
    else // 空白のボタン
    {
        // ボタンを順次追加していく
        for(int i = 0; i < nIDCount; i++)
            SendMessage(m_hWnd, TB_ADDBUTTONS, 0, (LPARAM)&btn);
    }

    SendMessage(m_hWnd, TB_AUTOSIZE, 0, 0);
}
//----------------------------------------------------------------------------

 この CToolBar クラスを継承した CToolBar_MainWnd などというクラスを作成し、メインウィンドウ用のツールバーを管理させます。その前にまず、ツールバー用のリソースを作成しておく必要がありますね。VC++ であれば、リソースの追加からツールバーを作成すればオッケーです。

 それでは、CToolBar_MainWnd クラスです。

//----------------------------------------------------------------------------
// ToolBar_MainWnd.h : メインウィンドウ用ツールバーの作成・管理を行う
//----------------------------------------------------------------------------
#ifndef ToolBar_MainWndH
#define ToolBar_MainWndH

class CApp;
class CMainWnd;
#include "../Common/ToolBar.h"
//----------------------------------------------------------------------------
// メインウィンドウ用ツールバーの作成・管理を行うクラス
//----------------------------------------------------------------------------
class CToolBar_MainWnd : public CToolBar
{
public: // 関数

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

    virtual BOOL Create();

private: // メンバ変数

    CApp & m_rApp;
    CMainWnd & m_rMainWnd;

public: // 定数

    // コマンド ID
    enum {
        ID_HEAD = 11,
        ID_PLAY,
        ID_PAUSE,
        ID_STOP,
        ID_NEXT,
        ID_OPENFILE,
        ID_SLOOP,
        ID_ALOOP,
        ID_RANDOM,
        ID_ABLOOP_A,
        ID_ABLOOP_B,
        ID_CONTROLBAR,
        ID_LIST,
    };
};
//----------------------------------------------------------------------------

#endif
//----------------------------------------------------------------------------
// ToolBar_MainWnd.cpp : メインウィンドウ用ツールバーの作成・管理を行う
//----------------------------------------------------------------------------
#include <windows.h>
#include <commctrl.h>
#include "../resource.h"
#include "../App.h"
#include "MainWnd.h"
#include "ToolBar_MainWnd.h"
//----------------------------------------------------------------------------
// 作成
//----------------------------------------------------------------------------
BOOL CToolBar_MainWnd::Create()
{
    CToolBar::Create(m_rMainWnd);
    if(!m_hWnd) return FALSE;
    SetStyle(GetStyle() | CCS_TOP | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT);
    LoadBitmap(MAKEINTRESOURCE(IDR_TOOLBAR1));
    UINT id[17] = {
        ID_HEAD,
        ID_PLAY,
        ID_PAUSE,
        ID_STOP,
        ID_NEXT,
        0,
        ID_OPENFILE,
        0,
        ID_SLOOP,
        ID_ALOOP,
        ID_RANDOM,
        0,
        ID_ABLOOP_A,
        ID_ABLOOP_B,
        0,
        ID_CONTROLBAR,
        ID_LIST,
    };
    SetButtons(id, 17);
    Show(SW_SHOW);
    return TRUE;
}
//----------------------------------------------------------------------------

 これで、MainWnd.h で ToolBar_MainWnd.h をインクルードしておき、CToolBar_MainWnd m_toolBar; メンバ変数を持つようにし、コンストラクタで、

CMainWnd(CApp & app): m_rApp(app), m_menu(app, *this), m_toolBar(app, *this) { }

CMainWnd::OnCreate 関数内で、

// ツールバーの作成
if(!m_toolBar.Create())
{
    MessageBox(m_hWnd, TEXT("ツールバーの作成に失敗しました。"
                "アプリケーションを終了します。"), TEXT("エラー"), MB_ICONERROR);
    Destroy();
    return FALSE;
}

などとすれば、ツールバーが表示されるようになりました。ここまでのソースコードはこちら ( Hayaemon20060215_src.lzh ) 。

 次に、ウィンドウのサイズが変更されたときにツールバーのサイズもそれにあわせて変更するようにします。ウィンドウプロシージャには、ウィンドウのサイズが変更されると WM_SIZE というメッセージが飛んできますので、それが来たら、ツールバーのサイズを変更するようにします。まず、CWnd クラスに、

virtual void OnSize(UINT state, int cx, int cy) {
    WindowProc(WM_SIZE, (WPARAM)state, MAKELPARAM(cx, cy));
}

という関数を追加し、CWnd::WndProc の

case WM_COMMAND:
    pWnd->OnCommand((int)LOWORD(wParam), (HWND)lParam, (UINT)HIWORD(wParam));
    return 0;
case WM_CREATE:
    return pWnd->OnCreate((LPCREATESTRUCT)lParam);

となっているところを、

case WM_COMMAND:
    pWnd->OnCommand((int)LOWORD(wParam), (HWND)lParam, (UINT)HIWORD(wParam));
    return 0;
case WM_CREATE:
    return pWnd->OnCreate((LPCREATESTRUCT)lParam);
case WM_SIZE:
    pWnd->OnSize((UINT)wParam, (int)LOWORD(lParam), (int)HIWORD(lParam));
    return 0;

に変更します。そして CMainWnd に、先ほどの OnSize 関数をオーバーライドした、

virtual void OnSize(UINT state, int cx, int cy) {
    m_toolBar.OnSize(state, cx, cy);
    CFrameWnd::OnSize(state, cx, cy);
}

のような関数を追加します。これで、ウィンドウのサイズにあわせてツールバーのサイズも変更されるようになりました。ここまでのソースコードはこちら ( Hayaemon20060215_2_src.lzh ) 。

 次は、ツールバー上に一定時間カーソルがあわせられたときに、ツールチップを出すようにします。