← 都来码 / Dolaima← 例程索引c/demo/audio_all.c原文

c/demo/audio_all.c · 21887 字节 · 下载原文

/* audio_all.c —— **音频接口逐项体检**:把 `vml_ui.h` 里声音相关的接口挨个走一遍。
 * audio_all.c -- a per-item health check of the audio API declared in `vml_ui.h`.
 *
 * 与 `audio_test.c` 的分工:那个是"乐器式"演示(音阶/和弦/旋律/多点触控,要人按);
 * Split of labor with `audio_test.c`: that one is an "instrument style" demo (scales,
 * 这个是**自走式**的 —— 跑起来它自己一项一项往下走,每项在屏幕上写清"现在测什么、
 * chords, melody, multi-touch) and needs a human to press things; this one is
 * 期望听见什么",你只要坐着听。想单独验某一项,看下面「怎么只看一项」。
 * **self-driving** -- it walks the items one by one, printing on screen what it tests
 * and what you should hear. You just sit and listen. To check one item alone, see below.
 *
 * ## 怎么跑
 * ## How to run
 *
 *     vml run examples/c/audio_all.c          (手机 App 的命令行页)
 *     vml run examples/c/audio_all.c          (mobile App command line page)
 *     vmlcli Examples/c/audio_all.c           (桌面,不发声但会打印音频事件)
 *     vmlcli Examples/c/audio_all.c           (desktop: silent, but prints audio events)
 *
 * ## 每一步在验什么(听着对不对,一行一行对)
 * ## What each step verifies (listen and check item by item)
 *
 *   ① `ui_audio_beep` 三个音高        —— 220 / 880 / 1760Hz 依次响,**一个比一个高**
 *   1. `ui_audio_beep` three pitches  -- 220 / 880 / 1760Hz in turn, **each higher than the last**
 *   ② `ui_audio_beep` 连发            —— 连打两个:**只听见后一个**(单通道的老语义,这是设计)
 *   2. `ui_audio_beep` back to back   -- fire two: **only the later one is heard** (single channel
 * legacy semantics -- this is by design)
 *   ③ `ui_tone_on/off` 单音     —— 中央 C(do)一声,0.5 秒
 *   3. `ui_tone_on/off` one note -- middle C (do) for 0.5 second
 *   ④ 复音(和弦)              —— do-mi-sol **同时**响;屏幕会报"此刻 N 个声部"
 *   4. Polyphony (chord)        -- do-mi-sol sound **at the same time**; the screen prints
 * "voices = N right now"
 *   ⑤ 波形                      —— 同一个音,正弦/方波/锯齿/三角各一遍(音色不同、音高相同)
 *   5. Waveform                 -- the same pitch as sine / square / saw / triangle
 * (different timbre, same pitch)
 *   ⑥ `ui_tone_set_max_voices`      —— 上限压到 2,再起 4 个音:屏幕报的声部数应**不超过 2**
 *   6. `ui_tone_set_max_voices`     -- push the limit down to 2, then start 4 notes: the reported
 * voice count should be **at most 2**
 *   ⑦ `ui_tone_off_all`         —— 和弦淡出(不是硬切)
 *   7. `ui_tone_off_all`        -- fade the chord out (not a hard cut)
 *   ⑧ `ui_tone_panic`           —— 起一片音后**立刻**全停(急停用)
 *   8. `ui_tone_panic`          -- start a pile of notes then **immediately** stop all
 * (for emergency stop)
 *   ⑨ 音序器 `ui_sfx_*`         —— "叮—咚—叮"三个音先后响;中途报表里还剩几个槽
 *   9. Sequencer `ui_sfx_*`     -- "ding-dong-ding", three notes one after another; it also
 * reports how many slots are still taken
 *   ⑩ `ui_audio_set_volume`         —— 同一句 `ui_audio_beep`,30% 音量与 100% 各一遍(听音量差)
 *   10. `ui_audio_set_volume`       -- the same `ui_audio_beep` at 30% and at 100% (hear the volume
 * difference)
 *   ⑪ `ui_audio_play` 等四个     —— 文件播放那条链(播放/还在播/停/音量):**返回码打在屏幕上**
 *   11. `ui_audio_play` and three friends -- the file playback chain (play / is playing /
 * stop / volume): **the return codes go on screen**
 *   ⑫ `ui_vibrate_simple`              —— 震一下(不是声音,同属"音效与触感"这一组)
 *   12. `ui_vibrate_simple`            -- one buzz (not sound, but the same "audio and haptics" group)
 *
 * ## 桌面怎么验收(不用耳朵)
 * ## How to verify on the desktop (no ears needed)
 *
 * `vmlcli` 把每次发声按机器可读的格式打给 stderr(在 `CliVmlHost` 里),
 * `vmlcli` prints every sound event to stderr in a machine readable format (in `CliVmlHost`),
 * **两种接口两种行**,别按错的格式去 grep:
 * **two interfaces, two kinds of line** -- do not grep with the wrong format:
 *   · `ui_audio_beep`  → `[vml-audio] tone hz=… ms=… wave=… vol=…`
 *   · `ui_audio_beep` emits this "tone" line (see the fields above)
 *   · `ui_tone_*` → `[vml-audio] note-on ch=… note=… vel=… voices=N`
 *   · `ui_tone_*` emits this "note-on" line (see the fields above)
 *     (关音 `note-off`、以及 `ctl all_off|wave|max_voices|voices|panic`)
 *     (a `note-off` when a note ends, plus the `ctl all_off|wave|max_voices|voices|panic` lines)
 *
 * 判据与 `audio_test.c` 同源:
 * Same criteria as `audio_test.c`:
 *   · 第 ① 步:三行 `tone`,hz 依次 220 / 880 / 1760(音高有序);
 *   · step 1: three `tone` lines, hz = 220 / 880 / 1760 in that order (pitches ascending);
 *   · 第 ④ 步:`note-on … voices=3`(复音成立);
 *   · step 4: `note-on ... voices=3` (polyphony holds);
 *   · 第 ⑥ 步:`max_voices=2` 之后的行里 **voices 不超过 2**(上限生效);
 *   · step 6: after `max_voices=2`, no later line reports **more than 2 voices** (limit works);
 *   · 第 ⑨ 步:序列表的三个音要按 tick 先后出现三行 `note-on`;
 *   · step 9: the three sequencer notes must show up as three `note-on` lines in tick order;
 *   · 第 ⑩ 步:`volume=30` 那一行的 `vol=` 与后面 `volume=100` 那行不同。
 *   · step 10: the `vol=` on the `volume=30` line differs from the one on the `volume=100` line.
 *
 * ## ⚠ 手机上没有声音时先查这两条
 * ## ⚠ If there is no sound on the phone, check these two things first
 *
 *   1. **静音开关**:iOS 侧我们已把音频会话设成 `Playback`(忽略静音开关);
 *   1. **The silent switch**: on iOS we already set the audio session to `Playback`
 *      Android 侧没有这条限制。
 *      (which ignores the silent switch); Android has no such restriction.
 *   2. **音量**:第 ⑩ 步把主音量压到 30% —— 跑完记得会回到 100%(程序末尾会恢复)。
 *   2. **Volume**: step 10 pushes the master volume down to 30% -- it does go back to 100%
 * at the end of the program.
 *
 * ## ⚠ C 前端的坑(calc.c / audio_test.c 都踩过)—— **2026-10-04 复验:两条都已修好**
 * ## ⚠ C front end pitfalls (calc.c and audio_test.c both hit them) — **re-checked 2026-10-04: both are fixed**
 *
 *   · ~~**局部数组单独一行声明**(与标量写在同一行会分不到槽位、读出来恒 0)~~;
 *   · ~~**Declare a local array on its own line** (sharing a line with a scalar leaves it
 * without a slot, so it always reads back as 0)~~;
 *     ✅ 已修好:`int i, m[4], t;` 后 `m[1]+m[2]` 实测 **50**。
 *     ✅ fixed: with `int i, m[4], t;`, `m[1]+m[2]` measured **50**.
 *   · ⚠ 调 `syscall` 一律走 `vml_ui.h` 的包装函数(**仍成立**),不要自己写 `asm("SYSCALL …")` 传**全局量** ——
 *   · ⚠ Always call syscalls through the `vml_ui.h` wrappers (**still holds**); do not write your own `asm("SYSCALL …")` that passes a **global** --
 *     那会**一条装载指令都不生成**、参数静默丢掉(传局部量/形参是好的)。
 *     it emits **no load instruction at all** and silently drops the argument (passing locals/parameters is fine).
 */

#include <vml_ui.h>

/* ── 小工具 ─────────────────────────────────────────────────────────── */
/* ── Small helpers ───────────────────────────────────────────────────── */

/* 忙等(不依赖定时器/消息循环 —— 这个程序不开窗,没有消息可等)。
 * Busy wait (no timer, no message loop -- this program opens no window, so there are
 * ⚠ 别改成 `ui_msg_wait`:那要窗口那套消息泵,这里没有。
 * no messages to wait for).
 * Do NOT change this to `ui_msg_wait`: that needs the window message pump, which we have
 * not got here. */
void pause_ms(int ms) {
    int t0;
    t0 = ui_time_get_ms();
    while (ui_time_get_ms() - t0 < ms) {
        /* 空转 */
        /* idle spin */
    }
}

void say(char* s) {
    printf("%s\n", s);
}

/* 打印一行"本步在验什么" —— 每步都带序号,方便对着上面的清单看。 */
/* Print one line saying "what this step verifies" -- numbered, so you can line it up
 * with the list above. */
void step_title(int n, char* what, char* expect, int lang) {
    printf(lang == 0 ? "\n【%d】%s\n" : "\n[%d] %s\n", n, what);
    printf(lang == 0 ? "     期望:%s\n" : "     expect: %s\n", expect);
}

/* ── 正题 ───────────────────────────────────────────────────────────── */
/* ── The main event ─────────────────────────────────────────────────── */

int main(void) {
    int i;
    int v;
    int lang;

    lang = ui_scr_get_language();   /* 界面语言:开局查一次(ui_scr_get_language 是 syscall,别每帧调) */
                                /* UI language: queried once at start (do not call it every frame) */

    printf("=============================================\n");
    printf(lang == 0 ? " 音频接口体检开始(共 12 项,约 25 秒)\n"
                     : " Audio API check: 12 items, about 25 seconds\n");
    printf(lang == 0 ? " 听不清就把音量调大;每项之前会写明期望\n"
                     : " Turn the volume up if it is unclear; each item says what to expect\n");
    printf("=============================================\n");

    /* ① ui_audio_beep:三个音高 —— 验证「音符号/频率 → 真的响」且高低有序 */
    /* 1. ui_audio_beep: three pitches -- verify "note number / frequency really sounds" and in order */
    step_title(1, lang == 0 ? "ui_audio_beep(频率, 毫秒):220 / 880 / 1760Hz"
                            : "ui_audio_beep(freq, ms): 220 / 880 / 1760Hz",
               lang == 0 ? "三声,一个比一个高"
                         : "three beeps, each higher than the last", lang);
    ui_audio_beep(220, 260);
    pause_ms(420);
    ui_audio_beep(880, 260);
    pause_ms(420);
    ui_audio_beep(1760, 260);
    pause_ms(600);

    /* ② ui_audio_beep 连发:单通道语义 —— 只听见最后一个(这不是 bug,是它的老语义) */
    /* 2. ui_audio_beep back to back: single channel -- only the last one is heard (not a bug) */
    step_title(2, lang == 0 ? "ui_audio_beep 连发两声(880 与 1760)"
                            : "ui_audio_beep fired twice in a row (880 then 1760)",
               lang == 0 ? "只听见后一声(单通道:后音掐前音)"
                         : "only the second one is heard (single channel: the later note cuts the earlier)", lang);
    ui_audio_beep(880, 300);
    ui_audio_beep(1760, 300);
    pause_ms(700);

    /* ③ ui_tone_on/off:单音(中央 C = 60) */
    /* 3. ui_tone_on/off: a single note (middle C = 60) */
    step_title(3, lang == 0 ? "ui_tone_on(0, 60, 100) 起一个音,0.5 秒后关"
                            : "ui_tone_on(0, 60, 100) starts a note, off 0.5 s later",
               lang == 0 ? "一声中央 C(do)"
                         : "one middle C (do)", lang);
    ui_tone_on(0, 60, 100);
    pause_ms(500);
    ui_tone_off(0, 60);
    pause_ms(300);

    /* ④ 复音:三个通道同时按 —— 这是 v0.96.485 改造的核心 */
    /* 4. Polyphony: three channels pressed at once -- the heart of the v0.96.485 rework */
    step_title(4, lang == 0 ? "三个通道同时起音:do(60) / mi(64) / sol(67)"
                            : "three channels start together: do(60) / mi(64) / sol(67)",
               lang == 0 ? "三个音**同时**响(和弦),屏幕上的声部数应为 3"
                         : "all three notes sound **at once** (a chord); voices on screen should be 3", lang);
    ui_tone_on(0, 60, 100);
    ui_tone_on(1, 64, 100);
    ui_tone_on(2, 67, 100);
    pause_ms(200);
    v = ui_tone_get_voices();
    printf(lang == 0 ? "     ▶ 此刻声部数 = %d(期望 3)\n"
                     : "     ▶ voices right now = %d (expect 3)\n", v);
    pause_ms(900);
    ui_tone_off(0, 60);
    ui_tone_off(1, 64);
    ui_tone_off(2, 67);
    pause_ms(300);

    /* ⑤ 波形:同一个音高、四种音色 */
    /* 5. Waveform: one pitch, four timbres */
    step_title(5, lang == 0 ? "ui_tone_set_wave:正弦 / 方波 / 锯齿 / 三角"
                            : "ui_tone_set_wave: sine / square / saw / triangle",
               lang == 0 ? "四声**音高相同、音色不同**"
                         : "four notes, **same pitch, different timbre**", lang);
    for (i = 0; i < 4; i = i + 1) {
        ui_tone_set_wave(3, i);
        ui_tone_on(3, 69, 100);   /* A4 = 440Hz */
        pause_ms(420);
        ui_tone_off(3, 69);
        pause_ms(140);
    }

    /* ⑥ ui_tone_set_max_voices:把上限压到 2,再起 4 个音 */
    /* 6. ui_tone_set_max_voices: push the limit down to 2, then start 4 notes */
    step_title(6, lang == 0 ? "ui_tone_set_max_voices(2) 后起 4 个音"
                            : "start 4 notes after ui_tone_set_max_voices(2)",
               lang == 0 ? "声部数**不超过 2**(上限生效,多出的被抢占)"
                         : "voices **at most 2** (the limit works; extras are stolen)", lang);
    ui_tone_set_max_voices(2);
    ui_tone_on(4, 60, 90);
    ui_tone_on(5, 64, 90);
    ui_tone_on(6, 67, 90);
    ui_tone_on(7, 72, 90);
    pause_ms(200);
    v = ui_tone_get_voices();
    printf(lang == 0 ? "     ▶ 此刻声部数 = %d(期望 ≤ 2)\n"
                     : "     ▶ voices right now = %d (expect <= 2)\n", v);
    pause_ms(800);
    ui_tone_off_all();
    ui_tone_set_max_voices(32);        /* 复原,别把后面的测试压着 */
    /* restore it, so it does not throttle the later tests */
    pause_ms(400);

    /* ⑦ ui_tone_off_all:淡出(听感上"渐渐没了"而不是"啪一下断") */
    /* 7. ui_tone_off_all: fade out (it fades away instead of cutting off with a snap) */
    step_title(7, lang == 0 ? "ui_tone_off_all():和弦淡出"
                            : "ui_tone_off_all(): fade the chord out",
               lang == 0 ? "声音**渐弱**消失(不是硬切)"
                         : "the sound **fades away** (not a hard cut)", lang);
    ui_tone_on(0, 60, 100);
    ui_tone_on(1, 64, 100);
    ui_tone_on(2, 67, 100);
    pause_ms(600);
    ui_tone_off_all();
    pause_ms(700);

    /* ⑧ ui_tone_panic:立刻全停(急停 / 强制停止那种场合) */
    /* 8. ui_tone_panic: stop everything at once (for emergency / forced stop) */
    step_title(8, lang == 0 ? "ui_tone_panic():立刻全停"
                            : "ui_tone_panic(): stop everything at once",
               lang == 0 ? "起一片音后**当场断掉**(急停语义)"
                         : "a pile of notes **cut off on the spot** (emergency stop)", lang);
    ui_tone_on(0, 60, 100);
    ui_tone_on(1, 64, 100);
    ui_tone_on(2, 67, 100);
    pause_ms(600);
    ui_tone_panic();
    pause_ms(500);

    /* ⑨ 音序器:三个音先后响(机制在共享库里,见 ui_sfx_* 的说明)
     * 9. Sequencer: three notes one after another (the machinery is in the shared
     *    ⚠ 本项**只验"音序器有没有推进"**。这三声之间是会重叠的(音序器的常态),
     * library, see the ui_sfx_* notes).
     *    ⚠ This item **only checks whether the sequencer advances**. Those three notes do
     *      而重叠正是真机上破音的原因之一 —— 听到"滋滋"属于已知现象(见内置帮助
     * overlap, which is normal for a sequencer, and that overlap is one of the causes of
     *      「音效与触感」那节),**不是本项失败**。游戏音效别用音序器,用 ui_audio_beep。
     * the crackle heard on real devices -- so a "zzz" noise is a known phenomenon (see
     * the built-in help, the audio and haptics section), **not a failure of this item**.
     * For game sound effects do not use the sequencer, use ui_audio_beep. */
    step_title(9, lang == 0 ? "音序器 ui_sfx_add ×3 + ui_sfx_tick"
                            : "sequencer ui_sfx_add ×3 + ui_sfx_tick",
               lang == 0 ? "三个音**先后**响(重叠属常态,真机上可能破音)"
                         : "three notes sound **one after another** (overlap is normal; may crackle on a real device)", lang);
    ui_sfx_reset();
    ui_sfx_add(0, 84, 0, 2, 80, VML_WAVE_SQUARE);   /* do,立刻响 2 拍 */
    /* do -- sounds at once, for 2 ticks */
    ui_sfx_add(1, 79, 2, 2, 80, VML_WAVE_SQUARE);   /* sol,晚 2 拍 */
    /* sol -- 2 ticks later */
    ui_sfx_add(2, 84, 4, 3, 80, VML_WAVE_SQUARE);   /* do,再晚 2 拍 */
    /* do -- 2 ticks after that */
    printf(lang == 0 ? "     ▶ 表里占用的槽 = %d(期望 3)\n"
                     : "     ▶ slots used in the table = %d (expect 3)\n", ui_sfx_get_active());
    for (i = 0; i < 10; i = i + 1) {
        ui_sfx_tick();
        pause_ms(120);
    }
    printf(lang == 0 ? "     ▶ 放完后槽 = %d(期望 0)\n"
                     : "     ▶ slots after playing = %d (expect 0)\n", ui_sfx_get_active());
    ui_sfx_panic();
    pause_ms(300);

    /* ⑩ ui_audio_set_volume:主音量对合成音也生效 */
    /* 10. ui_audio_set_volume: the master volume also applies to synthesised tones */
    step_title(10, lang == 0 ? "ui_audio_set_volume(30) 再 ui_audio_beep"
                             : "ui_audio_set_volume(30) then ui_audio_beep",
               lang == 0 ? "这一声**明显比前两声小**"
                         : "this one is **clearly quieter than the previous two**", lang);
    ui_audio_set_volume(30);
    ui_audio_beep(880, 400);
    pause_ms(700);
    ui_audio_set_volume(100);
    printf(lang == 0 ? "     ▶ 音量已恢复到 100\n" : "     ▶ volume restored to 100\n");
    ui_audio_beep(880, 400);
    pause_ms(700);

    /* ⑪ 文件播放那条链:ui_audio_play / stop / playing / volume
     * 11. The file playback chain: ui_audio_play / stop / playing / volume
     *    ⚠ 这一项**听不到声音**是正常的 —— 这里故意用一个不存在的文件,验的是
     *    ⚠ It is normal that you **hear nothing** here -- we deliberately use a file that
     *      「接口在、返回码合理、不崩」。真想听 BGM,往工作区放一个 mp3/wav 再把路径换掉。
     * does not exist, so what we check is "the interface is there, the return codes are
     * sane, nothing crashes". To really hear BGM, drop an mp3/wav into the workspace
     * and swap the path. */
    step_title(11, lang == 0 ? "ui_audio_play(\"no_such_file.wav\", 0) 等的返回码"
                             : "ui_audio_play(\"no_such_file.wav\", 0) and friends: return codes",
               lang == 0 ? "打印出返回码;**不响也不崩**(本项验接口存在与容错)"
                         : "it prints the return codes; **no sound and no crash** (checks the API exists and tolerates errors)", lang);
    v = ui_audio_play("no_such_file.wav", 0);
    printf(lang == 0 ? "     ▶ ui_audio_play 返回 %d\n"
                     : "     ▶ ui_audio_play returned %d\n", v);
    v = ui_audio_is_playing();
    printf(lang == 0 ? "     ▶ ui_audio_is_playing 返回 %d\n"
                     : "     ▶ ui_audio_is_playing returned %d\n", v);
    ui_audio_set_volume(80);
    ui_audio_stop();
    printf(lang == 0 ? "     ▶ ui_audio_stop 已调用\n" : "     ▶ ui_audio_stop called\n");
    pause_ms(400);

    /* ⑫ ui_vibrate_simple:不是声音,但同属"音效与触感"那一组 */
    /* 12. ui_vibrate_simple: not sound, but it belongs to the same audio-and-haptics group */
    step_title(12, "ui_vibrate_simple(200, 0)",
               lang == 0 ? "手机震一下(模拟器/桌面无感属正常)"
                         : "the phone buzzes once (no buzz on a simulator/desktop is normal)", lang);
    ui_vibrate_simple(200, 0);
    pause_ms(600);

    printf("\n=============================================\n");
    printf(lang == 0 ? " 体检结束。逐项对不上时:先看静音开关,\n"
                     : " Check finished. If an item does not match: first check the silent switch,\n");
    printf(lang == 0 ? " 再看是不是被 ⑥ 压过声部上限(本程序已复原)。\n"
                     : " then check whether step 6 capped the voice limit (this program restored it).\n");
    printf("=============================================\n");
    return 0;
}