🔉

DXライブラリでADX2LEを使って音量調整オプションを作るまで[2.音再生編]

2024/11/20に公開

前書き

前回はツール(CriAtomCraft)を使ってACFファイル及びACBファイルを作成しました。
https://zenn.dev/takodevlog/articles/6b4a11d4991991

今回はこれを使ってDXライブラリで音を再生するところまで進めていきます。

ちなみに、DXライブラリが最低限動作する設定が済んだ状態のプロジェクトができている前提で進めていきます。
(実行したらとりあえずウィンドウが出るところまで)
DXライブラリに必要なファイルも公式にならってC:直下に配置しています。(C:\DxLiv_VC\プロジェクトに追加すべきファイル_VC用)
以下に一応公式の設定マニュアルを置いておきます。
https://dxlib.xsrv.jp/use/dxuse_vscom2022.html

今回は特にサウンド関連のファイル分割やプリコンパイル済みヘッダーについて触れず、とにかく動作確認が目的なのでほぼ全てMain.cppで完結するコードになります。
多少読みにくいかもしれませんがご了承ください。
DXライブラリの動作確認をした初期状態は以下の通りです。(文字テスト描画、ウィンドウ起動、メインループ、ESCで終了設定有)

Main.cpp
#include "DxLib.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	// DxLib初期設定
	ChangeWindowMode(TRUE);
	SetGraphMode(800, 600, 32);
	SetDrawScreen(DX_SCREEN_BACK);
	SetMainWindowText("ADXTest");
	if (DxLib_Init() == -1) { return -1; }

	while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
	{
		ClearDrawScreen();
		DrawString(100, 100, "ADX Test", GetColor(255, 255, 255));
		ScreenFlip();
	}

	DxLib_End();
	return 0;
}

プロジェクトへファイル追加

DXライブラリの実行が確認できている状態で、まずは必要なファイルをプロジェクトに追加していきます。
今回はプロジェクト直下に「cri」という名前でプロジェクトにフォルダを追加し、
その中に前回DLしたSDKのpcフォルダに入っている「include」フォルダと「libs」フォルダを中身ごと持ってきます。
さらにcriフォルダの中に「data」フォルダを追加し、前回最後にビルドしたACF/ACBファイルとヘッダーファイルを全て入れます。

必要なファイルが揃ったらプロジェクトのプロパティを設定していきましょう。

プロジェクトのプロパティ設定

画面上部のプロジェクトメニューからプロパティを開きます。(Alt+F7でも可)
(ここから先の設定のパスは先ほどのプロジェクト直下のcriフォルダに必要フォルダが入っている前提となるので注意)
※構成はひとまずDebugのみ設定します。

  1. C/C++ > 全般 の「追加のインクルードディレクトリ」に以下追加
$(ProjectDir)cri\include


追加したら右下適用ボタンを押します(以下毎回同じ)

  1. リンカー > 全般 の「追加のライブラリディレクトリ」に以下追加
$(ProjectDir)cri\libs\x64

  1. リンカー > 入力 の「追加の依存ファイル」に以下追加
cri_ware_pcx64_le_import.lib

全て適用したらOKで閉じます。

Main.cppの上部にインクルードを追加してエラーが無ければおそらく問題なく設定できています。

Main.cpp
#include "DxLib.h"

+ // ADX2LE
+ // CRI SDKヘッダー
+ #include <cri_le_xpt.h>
+ // CRI ADXヘッダー
+ #include <cri_le_atom_ex.h>
+ #include <cri_le_atom_wasapi.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

初期化とテスト再生、終了処理

まずマクロ関連の定義と必要な関数の定義、そして初期化部分を記述します。
(追記量が多いので差分表示はしていません)

Main.cpp
#include "DxLib.h"

// ADX2LE
// CRI SDKヘッダー
#include <cri_le_xpt.h>
// CRI ADXヘッダー
#include <cri_le_atom_ex.h>
#include <cri_le_atom_wasapi.h>

// ACBのヘッダーファイル読み込み
#include "cri\data\BGM.h"
#include "cri\data\SE.h"

// 使用するファイル名
#define ACF_FILE        "cri/data/SampleProject.acf"
#define ACB_FILE_BGM    "cri/data/BGM.acb"
#define ACB_FILE_SE     "cri/data/SE.acb"

// 最大ボイス数を増やすための関連パラメータ
#define MAX_VOICE           (24)
#define MAX_VIRTUAL_VOICE   (64)
#define MAX_CRIFS_LOADER    (64)

// エラーコールバック関数
static void UserErrorCallbackFunc(const CriChar8* errid, CriUint32 p1, CriUint32 p2, CriUint32* parray)
{
	const CriChar8* errmsg;
	// エラー文字列の表示 
	errmsg = criErr_ConvertIdToMessage(errid, p1, p2);
	return;
}
// メモリ確保
void* UserAllocFunc(void* obj, CriUint32 size)
{
	void* ptr;
	ptr = malloc(size);
	return ptr;
}
// 解放
void UserFreeFunc(void* obj, void* ptr) { free(ptr); }

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // ADX用変数
    CriAtomExPlayerHn player;
    CriAtomExVoicePoolHn pool;
    CriAtomExAcbHn bgm_acb_hn;
    CriAtomExAcbHn se_acb_hn;

    // DxLib初期設定
    ChangeWindowMode(TRUE);
    SetGraphMode(800, 600, 32); // 800x600
    SetDrawScreen(DX_SCREEN_BACK);
    SetMainWindowText("ADXTest");
    if (DxLib_Init() == -1) { return -1; }

    // ADX初期設定
    // エラーコールバック関数の登録
    criErr_SetCallback(UserErrorCallbackFunc);
    // メモリアロケーターの登録
    criAtomEx_SetUserAllocator(UserAllocFunc, UserFreeFunc, NULL);
    // ライブラリの初期化
    CriAtomExConfig_WASAPI lib_config;
    CriFsConfig fs_config;
    criAtomEx_SetDefaultConfig_WASAPI(&lib_config);
    criFs_SetDefaultConfig(&fs_config);
    lib_config.atom_ex.max_virtual_voices = MAX_VIRTUAL_VOICE;
    fs_config.num_loaders = MAX_CRIFS_LOADER;
    lib_config.atom_ex.fs_config = &fs_config;
    criAtomEx_Initialize_WASAPI(&lib_config, NULL, 0);

    // ACFファイルの読み込みと登録
    criAtomEx_RegisterAcfFile(NULL, ACF_FILE, NULL, 0);
    // ACBファイルを読み込み、ACBハンドルを作成
    bgm_acb_hn = criAtomExAcb_LoadAcbFile(NULL, ACB_FILE_BGM, NULL, NULL, NULL, 0);
    se_acb_hn = criAtomExAcb_LoadAcbFile(NULL, ACB_FILE_SE, NULL, NULL, NULL, 0);
    // ボイスプールの作成
    CriAtomExStandardVoicePoolConfig standard_vpool_config;
    criAtomExVoicePool_SetDefaultConfigForStandardVoicePool(&standard_vpool_config);
    standard_vpool_config.num_voices = MAX_VOICE;
    pool = criAtomExVoicePool_AllocateStandardVoicePool(&standard_vpool_config, NULL, 0);
    // プレイヤーの作成
    player = criAtomExPlayer_Create(NULL, NULL, 0);

    // BGM(ステージ1)の再生テスト
    criAtomExPlayer_SetCueId(player, bgm_acb_hn, CRI_BGM_BGM_STAGE1);
    criAtomExPlayer_Start(player);

    while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        //==================================================
        // Update
        //==================================================
        // サーバ処理の実行
        criAtomEx_ExecuteMain();

        //==================================================
        // Render
        //==================================================
        ClearDrawScreen();
        DrawString(20, 560, "Esc:Exit", GetColor(255, 255, 255));
        ScreenFlip();
    }

    // Atomハンドルの破棄
    criAtomExPlayer_Destroy(player);
    // ボイスプールの破棄
    criAtomExVoicePool_Free(pool);
    // ACBハンドルの破棄
    criAtomExAcb_Release(bgm_acb_hn);
    criAtomExAcb_Release(se_acb_hn);
    // ACFの登録解除
    criAtomEx_UnregisterAcf();
    // Atom:ライブラリの終了
    criAtomEx_Finalize_WASAPI();

    DxLib_End();
    return 0;
}

実行前にビルドデータのフォルダにdllとデータをコピーしておきます。
cri\libs\x64にあるcri_ware_pcx64_le.dllを、exeがあるフォルダ(x64\Debug)にコピーしてください。
exeを直接実行して動作確認したい場合はここにcri\dataフォルダとその中身一式も必要になります。

#define ACB_FILE_BGM    "cri/data/BGM.acb"
#define ACB_FILE_SE     "cri/data/SE.acb"

上記の部分についてはプロジェクトで作ったキューシートの数や名前で異なるので参考程度としてください。
(そのあとのACFやACBを扱う部分は全て同上)

最初の方に定義している「User~」関数は初期設定時に必要な関数だったのでADXのチュートリアルの方からそのまま移植しました。

再生までの全体の手順としては、

  1. 変数の用意
  2. ADX初期設定
  3. ACF・ACB読み込み
  4. ボイスプール・プレイヤー作成
  5. キューを指定して再生

という流れです。

例によって、細かい単語の意味などは公式の用語集を参照してください。

再生確認

先ほどのコードで実際に音を再生しているのがこの部分です。とりあえずBGMを1曲鳴らしてみました。

Main.cpp
    // BGM(ステージ1)の再生テスト
    criAtomExPlayer_SetCueId(player, bgm_acb_hn, CRI_BGM_BGM_STAGE1);
    criAtomExPlayer_Start(player);

criAtomExPlayer_SetCueId()でプレイヤーとACBを指定した上で、そのキューシートのキューIDを指定します。
キューIDはそれぞれのACBに対応するヘッダファイル(今回ならBGM.h)にマクロが載っています。
(※以下は筆者の場合のBGM.hの中身なので、マクロ名は自分のヘッダーファイルを確認してください)

cri\data\BGM.h
/* Cue List (Cue ID) */
#define CRI_BGM_BGM_STAGE1           ( 0) /*  */
#define CRI_BGM_BGM_STAGE2           ( 1) /*  */
#define CRI_BGM_BGM_LASTBOSS         ( 2) /*  */

キューシート名が「BGM」でキュー名が「bgm_stage1」だったのでマクロ名が「CRI_BMG_BGM_STAGE1」という奇妙な名前になっているのは一旦目をつぶるとして(気になる場合はツール側で名前直してACBビルドしなおし)
criAtomExPlayer_Start(player);でプレイヤーにセットしたキューを再生する、というイメージです。

プレイヤー1つで複数キューの再生が可能なので、
キー入力に合わせて音を鳴らすサンプルを作成してみます。
(DXライブラリだとデフォルトでキーボードの押した瞬間判定ができないため、別途Keyboardクラスを作成し実装を行っています。)
長いので開いて確認を↓

Keyboardクラス
Keyboard.h
#pragma once
#include <array>
class Keyboard
{
public:
    Keyboard();
    virtual ~Keyboard() = default;

    /// <summary>更新処理</summary>
    void Update();

    /// <summary>押された瞬間か</summary>
    /// <param name="key">DxLibキー指定マクロ</param>
    bool IsKeyDown(int key) const;

    /// <summary>押している状態か</summary>
    /// <param name="key">DxLibキー指定マクロ</param>
    bool IsKeyHold(int key) const;

    /// <summary>離された瞬間か</summary>
    /// <param name="key">DxLibキー指定マクロ</param>
    bool IsKeyUp(int key) const;

private:
    // キーボードバッファの最大
    static constexpr int kKeyboardBufSize = 256;

    // キーボード入力の状態
    enum KeyState
    {
        kFree = 0,	// 押されていない
        kDown = 1,	// 押した瞬間
        kHold = 2,	// 押されている(長押し)
        kUp = 3		// 離された瞬間
    };

    // キーボード入力のバッファ
    std::array<char, kKeyboardBufSize>		m_key_buf_;
    // キーボード入力の状態
    std::array<KeyState, kKeyboardBufSize>	m_key_state_;
};
Keyboard.cpp
#include "DxLib.h"
#include "Keyboard.h"

Keyboard::Keyboard()
{
    m_key_buf_.fill(0);
    m_key_state_.fill(kFree);
}

void Keyboard::Update()
{
    GetHitKeyStateAll(m_key_buf_.data());
    for (int i = 0; i < kKeyboardBufSize; i++)
    {
        // キーが押されている
        if (m_key_buf_[i])
        {
            // 前フレームで押されていなければDownへ
            if (m_key_state_[i] == kFree) { m_key_state_[i] = kDown; }
            // 前フレームでDownならHoldへ
            else if (m_key_state_[i] == kDown) { m_key_state_[i] = kHold; }
        }
        // キーが押されていない
        else
        {
            // 前フレームで押されていた場合Upへ
            if (m_key_state_[i] == kDown || m_key_state_[i] == kHold)
            {
                m_key_state_[i] = kUp;
            }
            else { m_key_state_[i] = kFree; }
        }
    }
}

// キーを押し続けているか
bool Keyboard::IsKeyHold(int key) const
{
    if (m_key_state_[key] == kHold) { return true; }
    return false;
}

// キーを押した瞬間
bool Keyboard::IsKeyDown(int key) const
{
    if (m_key_state_[key] == kDown) { return true; }
    return false;
}

// キーを離した瞬間
bool Keyboard::IsKeyUp(int key) const
{
    if (m_key_state_[key] == kUp) { return true; }
    return false;
}
BGM/SE切り替えテスト

Main.cppは全て載せると長いので前後の行を見て察してもらえれば…m(_ _)m
(また、各キーで鳴らしている音は筆者のプロジェクトで追加した音なのでご自身のACBにあるマクロ名に適宜読み替えてもらえればと)

Main.cpp
+ #include <memory>
#include "DxLib.h"
+ #include "Keyboard.h"
    CriAtomExAcbHn se_acb_hn;

    // 入力用
+   std::unique_ptr<Keyboard> p_keyboard = std::make_unique<Keyboard>();

    // DxLib初期設定
    ChangeWindowMode(TRUE);
    // プレイヤーの作成
    player = criAtomExPlayer_Create(NULL, NULL, 0);

-   // BGM(ステージ1)の再生テスト
-   criAtomExPlayer_SetCueId(player, bgm_acb_hn, CRI_BGM_BGM_STAGE1);
-   criAtomExPlayer_Start(player);

    while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        //==================================================
        // Update
        //==================================================
+       // 入力の更新
+       p_keyboard->Update();

        // サーバ処理の実行
        criAtomEx_ExecuteMain();
+       // 発音テスト
+       if (p_keyboard->IsKeyDown(KEY_INPUT_1))
+       {
+           criAtomExPlayer_SetCueId(player, bgm_acb_hn, CRI_BGM_BGM_STAGE1);
+           criAtomExPlayer_Start(player);
+       }
+       if (p_keyboard->IsKeyDown(KEY_INPUT_2))
+       {
+           criAtomExPlayer_SetCueId(player, bgm_acb_hn, CRI_BGM_BGM_STAGE2);
+           criAtomExPlayer_Start(player);
+       }
+       if (p_keyboard->IsKeyDown(KEY_INPUT_3))
+       {
+           criAtomExPlayer_SetCueId(player, bgm_acb_hn,CRI_BGM_BGM_LASTBOSS);
+           criAtomExPlayer_Start(player);
+       }
+       if (p_keyboard->IsKeyDown(KEY_INPUT_Q))
+       {
+           criAtomExPlayer_SetCueId(player, se_acb_hn, CRI_SE_SE_PAUSE);
+           criAtomExPlayer_Start(player);
+       }
+       if (p_keyboard->IsKeyDown(KEY_INPUT_W))
+       {
+           criAtomExPlayer_SetCueId(player, se_acb_hn, CRI_SE_SE_ENEMY_HIT);
+           criAtomExPlayer_Start(player);
+       }
+       if (p_keyboard->IsKeyDown(KEY_INPUT_E))
+       {
+           criAtomExPlayer_SetCueId(player, se_acb_hn, CRI_SE_SE_FOOTSTEP);
+           criAtomExPlayer_Start(player);
+       }
+       // 停止
+       if (p_keyboard->IsKeyDown(KEY_INPUT_SPACE))
+       {
+           criAtomExPlayer_Stop(player);
+       }

        //==================================================
        // Render
        //==================================================
        ClearDrawScreen();
+       DrawString(20, 40, "1/2/3 Key:BGM", GetColor(255, 255, 255));
+       DrawString(20, 80, "Q/W/E Key:SE", GetColor(255, 255, 255));
+       DrawString(20, 120, "Space:StopAll", GetColor(255, 255, 255));
        DrawString(20, 560, "Esc:Exit", GetColor(255, 255, 255));
        ScreenFlip();

とりあえずツールの方で登録した6キュー全てを再生できるようにしてみました。(長いですが)
1/2/3キーでBGM3曲を切り替え、Q/W/Eキーで3種類の効果音再生、Spaceキーで全停止ができます。
BGMは切り替え時にクロスフェードがかかり、SEのピッチランダムなども反映されているのが確認できると思います。

おわり

前回のビルドデータを使って音の再生ができました。
次回はスライダーを作成し、それを使ってカテゴリごとにスライダーで音量調整できるようにして完成です。

次記事↓
https://zenn.dev/takodevlog/articles/36de23a1a44a9d

Discussion