Chapter 111 · 音频系统

背景音乐 Music

Music 专为长时间背景音乐设计,流式解码、低内存占用,支持循环、淡入淡出、暂停恢复。

111

概念

Music 与 Sound 的核心区别:
• Sound:全部解码到内存,低延迟,适合短音效
• Music:流式解码,内存占用恒定(不随文件时长增长),适合 3~10 分钟的背景音乐
同一时刻通常只有一首 Music 在播放,切换曲目时用 Fade 实现平滑过渡。

API 速查

函数说明
int xgeMusicLoad(xge_music pMusic, const char* sPath)加载音乐文件
int xgeMusicLoadGroup(xge_music pMusic, const char* sPath, xge_audio_group pGroup)加载到分组
int xgeMusicPlay(xge_music pMusic)播放
int xgeMusicStop(xge_music pMusic)停止
int xgeMusicPause(xge_music pMusic)暂停
int xgeMusicResume(xge_music pMusic)恢复
void xgeMusicSetLoop(xge_music pMusic, int bLoop)设置循环
void xgeMusicSetVolume(xge_music pMusic, float fVolume)设置音量
void xgeMusicFade(xge_music pMusic, float fFrom, float fTo, int iMilliseconds)音量渐变
int xgeMusicIsPlaying(xge_music pMusic)是否正在播放
void xgeMusicFree(xge_music pMusic)释放

编译本章示例

gcc -O2 -I. -o ch111.exe ch111_main.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch111.exe

示例:场景切换 + 淡入淡出

// 加载两首背景音乐
xge_music_t bgmMenu, bgmBattle;
xgeMusicLoad(&bgmMenu, "music/menu.ogg");
xgeMusicLoad(&bgmBattle, "music/battle.ogg");

// 主菜单:循环播放,淡入
xgeMusicSetLoop(&bgmMenu, 1);
xgeMusicSetVolume(&bgmMenu, 0.0f);
xgeMusicPlay(&bgmMenu);
xgeMusicFade(&bgmMenu, 0.0f, 0.7f, 1500);

// 进入战斗:淡出菜单 → 淡入战斗
void enterBattle(void) {
    xgeMusicFade(&bgmMenu, 0.7f, 0.0f, 1000);
    xgeMusicSetLoop(&bgmBattle, 1);
    xgeMusicSetVolume(&bgmBattle, 0.0f);
    xgeMusicPlay(&bgmBattle);
    xgeMusicFade(&bgmBattle, 0.0f, 0.8f, 1000);
}

// 暂停/恢复(如打开设置菜单)
xgeMusicPause(&bgmBattle);
xgeMusicResume(&bgmBattle);

// 退出时释放
xgeMusicFree(&bgmMenu);
xgeMusicFree(&bgmBattle);

完整代码

将以下代码保存为 ch111_main.c,编译后运行即可看到背景音乐播放与场景切换效果:

#include "xge.h"

static xge_music_t g_bgmMenu;
static xge_music_t g_bgmBattle;
static xge_font g_font = NULL;
static int g_scene = 0; // 0=menu, 1=battle

static void switchScene(int scene) {
    if (scene == g_scene) return;
    g_scene = scene;
    if (scene == 1) {
        // 淡出菜单音乐,淡入战斗音乐
        xgeMusicFade(&g_bgmMenu, 0.7f, 0.0f, 1000);
        xgeMusicSetVolume(&g_bgmBattle, 0.0f);
        xgeMusicPlay(&g_bgmBattle);
        xgeMusicFade(&g_bgmBattle, 0.0f, 0.8f, 1000);
    } else {
        xgeMusicFade(&g_bgmBattle, 0.8f, 0.0f, 1000);
        xgeMusicSetVolume(&g_bgmMenu, 0.0f);
        xgeMusicPlay(&g_bgmMenu);
        xgeMusicFade(&g_bgmMenu, 0.0f, 0.7f, 1000);
    }
}

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);

    // 按键切换场景
    if (xgeKeyPressed('1')) switchScene(0);
    if (xgeKeyPressed('2')) switchScene(1);
    if (xgeKeyPressed('P')) {
        if (g_scene == 0) xgeMusicPause(&g_bgmMenu);
        else xgeMusicPause(&g_bgmBattle);
    }
    if (xgeKeyPressed('R')) {
        if (g_scene == 0) xgeMusicResume(&g_bgmMenu);
        else xgeMusicResume(&g_bgmBattle);
    }

    // HUD
    xgeTextDraw(g_font, "1:Menu 2:Battle P:Pause R:Resume",
        50, 50, 0xFFFFFFFF);
    char buf[64];
    sprintf(buf, "Scene: %s",
        g_scene == 0 ? "Menu" : "Battle");
    xgeTextDraw(g_font, buf, 50, 90, 0xFFFFFFFF);

    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch111 - Music";
    d.iFlags  = XGE_INIT_VSYNC;
    xgeInit(&d);
    xgeAudioInit();

    // 加载两首背景音乐
    xgeMusicLoad(&g_bgmMenu, "music/menu.ogg");
    xgeMusicLoad(&g_bgmBattle, "music/battle.ogg");
    xgeMusicSetLoop(&g_bgmMenu, 1);
    xgeMusicSetLoop(&g_bgmBattle, 1);

    // 开始播放菜单音乐(淡入)
    xgeMusicSetVolume(&g_bgmMenu, 0.0f);
    xgeMusicPlay(&g_bgmMenu);
    xgeMusicFade(&g_bgmMenu, 0.0f, 0.7f, 1500);

    g_font = xgeFontLoad("res/font/default.ttf", 24);
    xgeRun(onFrame, NULL);

    xgeMusicFree(&g_bgmMenu);
    xgeMusicFree(&g_bgmBattle);
    xgeFontFree(g_font);
    xgeAudioUnit();
    xgeUnit();
    return 0;
}

代码讲解

  • xgeMusicLoad:流式加载,不会一次性解码全部文件,内存恒定。
  • xgeMusicSetLoop:背景音乐通常循环播放。
  • xgeMusicFade:淡入淡出过渡,切换曲目时避免突兀。
  • xgeMusicPause / Resume:暂停/恢复(如打开设置菜单)。
  • switchScene:封装场景切换逻辑,淡出旧曲 + 淡入新曲。

要点与陷阱

  • 流式解码:Music 不会一次性加载整个文件,内存恒定。
  • 单通道:通常同时只播一首,多首同时 Play 会混音。
  • Fade 过渡:切换曲目时先淡出旧曲再淡入新曲,避免突兀。
  • 格式:推荐 OGG Vorbis(体积小、质量好)。
  • 无 3D:Music 没有 SetPosition,始终全向播放。

进阶练习

  1. 实现播放列表:多首 Music 顺序播放,播完自动切下一首。
  2. 动态音乐系统:根据游戏紧张度实时调整音量/曲目。

已验证实现

权威源码为 examples/tutorial_capture/ch111_main1.c。它用自生成 WAV 验证 xgeMusicLoad 的流式音乐路径、循环、音量、播放、暂停、恢复和停止。教学测试验证状态机;实际音乐资源应使用有授权的长音频文件。

XGE music streaming lifecycle validation
图:第 111 章真实运行的音乐流状态验证。绿色表示该构建已成功加载和控制流式音乐对象。