杰理 SDK 文档中心
首页
首页
  • 入门指南

    • SDK 概述与芯片平台
    • 环境搭建与开发工具链
    • 编译、烧录与快速开始
  • 应用层开发

    • 语音玩具应用 voice_toy
    • 扩音器应用 voice_enhanced
    • 语音功能状态机 voice_func
    • 应用公共框架与配置
  • 音频子系统

    • 音频解码器与 MIDI 播放
    • 音频编码与录音
    • 音效算法(ANS、变调、变声、混响)
    • 音频输出、功放与硬件重采样
  • 存储与文件系统

    • 文件系统层(FAT、NOR_FS、SYDF 等)
    • 存储设备与设备管理
    • 参数存储 VM 与保留区
  • 系统机制

    • 消息与事件机制
    • 电源管理与低功耗
    • 固件升级机制
    • 外设驱动(按键、红外、SPI、USB)
    • 实时时钟与定时器
  • 构建系统与工具

    • 构建系统(Makefile 与 Code::Blocks)
    • 编译后处理与语音资源打包
  • 硬件平台与文档

    • 芯片平台与启动流程
    • 硬件文档、规格书与原理图

音频解码器与 MIDI 播放

本文档介绍 AD24N SDK 中 MIDI(乐谱解码 + 电子琴)子系统的完整实现:解码框架接入、MIDI 文件解析与音频合成引擎、音色表(Tone Table)加载、应用层播放/弹奏流程、配置项与 API,以及错误处理与扩展点。

Purpose and Scope

本页覆盖以下内容:

  • MIDI 解码器在解码框架(decoder framework)中的注册与调用流程(MIDI_LST / MIDI_CTRL_LST)
  • MIDI 乐谱解码(midi_dec)与合成播放引擎(midi_play)的内部机制
  • 音色表(SPI Flash 存放的乐器采样数据)的格式与加载方式
  • MIDI 消息定义(MIDIDefs.h)、事件处理与应用层控制(toy_midi / toy_midi_ctrl)
  • 相关配置项(app_config.c)与公开 API 参考

不属于本页范围、由其他页面覆盖的内容:通用解码器框架(decoder_api、DAC 注册)的通用机制、USB 设备层协议细节(usb_midi.c 仅作提及)、语音玩具(voice_toy)业务逻辑、以及音频前端(DAC/混音)本身。如需了解这些,请参见对应目录页。

Overview

AD24N 的 MIDI 子系统由两层组成:

  1. 解码器层(decoder):位于 sdk/app/bsp/common/decoder/list/,通过 midi_api.c(乐谱解码)与 midi_ctrl_api.c(电子琴/控制器模式)把 MIDI 能力封装成标准解码器接口(decoder_ops_t),从而复用整个 SDK 的 decoder_api 框架——包括文件读取、DAC 注册、播放控制(暂停/快进/停止)等通用机制。
  2. 合成模块层(module):位于 sdk/app/bsp/modules/midi/,包括 midi_dec.c(MIDI SMF 文件解析与事件调度)、midi_play.c(多声道采样合成器,基于 BasicSynth 引擎)、midi_event.c(事件回调)与 MIDIDefs.h(MIDI 消息常量定义)。

MIDI 乐谱解码器的特殊之处在于:它不是把编码音频流解码成 PCM,而是解析音符事件、按时间轴调度,并用内置合成器实时合成多音符混音。因此其核心参数是"最大同时发声的 key 数"(MAX_DEC_PLAYER_CNT / MAX_CTR_PLAYER_CNT),该值直接决定合成缓冲区大小与 CPU 开销。

系统支持两种运行模式(见 toy_main.c):

  • TOY_MIDI:播放 MIDI 乐谱文件(toy_midi_app())
  • TOY_MIDI_KEYBOARD:电子琴模式,实时响应按键/外部事件(toy_midi_keyboard_app())

Architecture

flowchart TD
    subgraph sg_App["应用层 (voice_func)"]
        ToyMain["toy_main.c<br/>work_mode 分发"]
        ToyMidi["toy_midi<br/>乐谱播放 App"]
        ToyMidiCtrl["toy_midi_ctrl<br/>电子琴 App"]
        AppCfg["app_config.c<br/>玩家数/通道配置"]
    end

    subgraph sg_Decoder["解码框架层 (decoder)"]
        DecoderApi["decoder_api.c<br/>MIDI_LST / MIDI_CTRL_LST"]
        MidiApi["midi_api.c<br/>midi_decode_api"]
        MidiCtrlApi["midi_ctrl_api.c<br/>键盘控制 API"]
    end

    subgraph sg_Module["MIDI 模块层 (modules/midi)"]
        MidiDec["midi_dec.c<br/>SMF 解析与事件调度"]
        MidiPlay["midi_play.c<br/>采样合成引擎"]
        MidiEvent["midi_event.c<br/>事件回调"]
        MidiDefs["MIDIDefs.h<br/>消息常量定义"]
    end

    subgraph sg_IO["硬件/IO 层"]
        ToneTab["SPI Flash 音色表<br/>(spi_pos)"]
        DAC["DAC 输出"]
        USB["usb_midi.c<br/>USB MIDI 设备"]
    end

    ToyMain -->|"TOY_MIDI"| ToyMidi
    ToyMain -->|"TOY_MIDI_KEYBOARD"| ToyMidiCtrl
    ToyMidi --> DecoderApi
    ToyMidiCtrl --> DecoderApi
    DecoderApi --> MidiApi
    DecoderApi --> MidiCtrlApi
    MidiApi --> MidiDec
    MidiCtrlApi --> MidiPlay
    MidiDec --> MidiPlay
    MidiPlay --> MidiEvent
    MidiPlay -->|"读取采样"| ToneTab
    MidiDec -->|"注册到 DAC"| DAC
    MidiCtrlApi --> USB
    AppCfg -.->|"全局配置"| MidiApi
    AppCfg -.->|"全局配置"| MidiPlay

架构说明:

  • 应用层通过 work_mode 选择 MIDI 工作模式,app_config.c 中导出的 const int(MAX_DEC_PLAYER_CNT 等)是贯穿解码器与合成器的全局配置。
  • 解码框架层是标准接入点:decoder_api.c 的解码器列表中注册了 MIDI_LST 与 MIDI_CTRL_LST(见 decoder_api.c),使 MIDI 与其他格式(MP3、AAC 等)共用同一套播放控制管线。
  • 合成模块层是核心:midi_play.c 的 MIDI_CTRL_OPEN / MIDI_CTRL_MAIN 负责把音符事件合成为 PCM 采样,并从 SPI 音色表读取乐器波形;MIDIDefs.h 提供标准的 MIDI 消息字节常量(源自 BasicSynth 库)。

解码器框架集成

SDK 的解码框架通过"解码器操作表"(decoder_ops_t)抽象各种格式。MIDI 解码器在 decoder_api.c 中被引入:

#include "midi_api.h"
#include "list/midi_ctrl_api.h"

并在解码器列表中占据 MIDI_LST 与 MIDI_CTRL_LST 两个槽位(见 decoder_api.c):

A_LST
MIDI_LST
MIDI_CTRL_LST

Sources:

  • decoder_api.c

MIDI_LST 对应乐谱解码(midi_api.c),MIDI_CTRL_LST 对应电子琴/控制器模式(midi_ctrl_api.c)。两者共用模块层的合成引擎,但事件来源不同:前者来自 MIDI 文件解析,后者来自实时控制事件(按键、USB MIDI 等)。

乐谱解码器初始化流程

解码入口是 midi_decode_api()(见 midi_api.c),它完成以下关键步骤:

  1. 检查音色表配置:静态变量 midi_tone_tab 为空时直接返回 E_MIDI_NO_CFG——MIDI 解码强依赖音色表,没有音色数据无法发声。
  2. 初始化解码句柄:关中断清空 dec_midi_hld(dec_obj),设置 type = D_TYPE_MIDI。
  3. 分配工作缓冲区:使用放置段 AT(.midi_buf) 的静态数组 midi_decode_buff_nomark[4436/4],并调用 ops->need_dcbuf_size() 校验缓冲区是否足够;不足则返回 E_MIDI_DBUF。缓冲区大小会随 MAX_DEC_PLAYER_CNT 变化。
  4. 初始化环形输出缓冲:cbuf_init(&cbuf_midi, &obuf_midi[0], sizeof(obuf_midi)),合成出的 PCM 通过该 cbuffer 送给 DAC。
  5. 获取文件名与 IO 接口:midi_dec_io0 把 mp_input / mp_output 作为解码器 IO 回调。
  6. 打开与格式检查:ops->open(...) 后调用 ops->format_check(...),格式错误返回 E_MIDIFORMAT。
  7. 配置采样率:读取 DAC 采样率 dac_sr_read(),通过 midi_musicsr_to_cfgsr() 映射为合成器支持的索引值(48000/44100/32000/24000/22050/16000/12000/11025/8000),不支持的值会触发 ASSERT(0, "midi error sr !!!:%d\n", sr)。
  8. 填写合成参数并下发:midi_t_parm.player_t = MAX_DEC_PLAYER_CNT(同时发声 key 数)、sample_rate、spi_pos = (u16 *)midi_tone_tab(音色表起始地址),随后调用弱函数 midi_init_info(&init_info) 供应用定制,最后 ops->dec_confing(p_cal_buf, CMD_INIT_CONFIG, &init_info)。
  9. 注册到 DAC:regist_dac_channel(&dec_midi_hld.sound, kick_decoder),此后解码输出由 DAC 任务驱动。

该流程揭示了设计意图:MIDI 解码器复用通用解码框架的生命周期(open → format_check → dec_confing → DAC 注册),但用一段专门的 .midi_buf 静态内存承载合成器工作区,避免大块动态分配;音色表被当作"必须的外部资源"在入口处强制校验。

合成引擎(midi_play.c)

midi_play.c 实现了基于采样回放的波表合成器。核心是 MIDI_CTRL_OPEN(见 midi_play.c):

static u32 MIDI_CTRL_OPEN(void *work_buf, void *dec_parm, void *parm)
{
    unsigned int chn;
    long long tmp64;
    MIDI_DECODE_VAR *mid_dec_obj = (MIDI_DECODE_VAR *)work_buf;
    MIDI_CONFIG_PARM *midi_param = (MIDI_CONFIG_PARM *)parm;
    MIDI_CTRL_PARM *midi_dec_param = (MIDI_CTRL_PARM *)dec_parm;

    EVENT_FIFO_CONTEXT *midi_fifo_t = (EVENT_FIFO_CONTEXT *)(&mid_dec_obj->smf_data);

    memset(work_buf, 0, sizeof(MIDI_DECODE_VAR));
    memcpy(midi_fifo_t, midi_dec_param, sizeof(MIDI_CTRL_PARM));

    u16 *cmporkind;
    cmporkind = (u16 *)midi_param->spi_pos;
    if (cmporkind[0] == 0xABCD) {
        mid_dec_obj->midi_spi_pos = (u8 *)&cmporkind[1];
        mid_dec_obj->compressIN = 1;
        mid_dec_obj->instr_spi = cmporkind[1];
        mid_dec_obj->instr_map = (unsigned short *)&cmporkind[130];
    } else {
        mid_dec_obj->midi_spi_pos = (u8 *)midi_param->spi_pos;
        mid_dec_obj->compressIN = 0;
        mid_dec_obj->instr_spi = cmporkind[0];
        mid_dec_obj->instr_map = (unsigned short *)&cmporkind[129];
    }

    mid_dec_obj->sample_rate = midi_param->sample_rate;
    mid_dec_obj->MAX_PLAYER_CNTt = midi_param->player_t;
    if (mid_dec_obj->MAX_PLAYER_CNTt > MAX_CTR_PLAYER_CNT) {
        mid_dec_obj->MAX_PLAYER_CNTt = MAX_CTR_PLAYER_CNT;
    }
    ...

Source: midi_play.c

关键设计点:

  • 音色表头部约定:音色数据首字为 0xABCD 时表示压缩格式,instr_spi(乐器数)与 instr_map(乐器映射表)分别位于偏移 1 与 130;否则为普通格式,乐器数与映射表位于偏移 0 与 129。这把"数据格式版本"编码进音色文件头部,兼容新旧音色文件。
  • 玩家数上限:MAX_PLAYER_CNTt 取自配置,但被硬上限 MAX_CTR_PLAYER_CNT 钳制,防止越界分配 MIDI_PLAYER 数组。
  • 通道初始化:16 个通道的 CC 默认值(音量 127、表情 127、声像 64)与 pitchBend_v = 256(无弯音)在此建立,确保初始状态符合 GM 规范。
  • 时间基准:srTicks 由 tempo / 4 与采样率表计算得出,MULSI 宏完成定点乘除,体现嵌入式环境避免浮点运算的优化策略。
  • 内存布局:mempool 中先分配 MIDI_PLAYER 数组(needMidiPlayersBuf()),随后是 play_key[MAX_CTR_PLAYER_CNT * MAX_CHANNEL_NUM](初值 255 表示空槽),即"玩家池 + 键位映射表"。

MIDI_CTRL_MAIN 是实时合成主循环:它按 srTicks 推进时间轴、解析排队的 MIDI 事件、为每个活跃音符调用采样插值(MIDI_RESAMPLE_SHIFT 13 位定点重采样)、叠加各通道混音并做音量/衰减处理,最终输出到 cbuffer。合成器的多发声能力(polyphony)完全由 MAX_DEC_PLAYER_CNT / MAX_CTR_PLAYER_CNT 决定,这也是 need_dcbuf_size() 随该参数变化的原因。

MIDI 消息定义(MIDIDefs.h)

MIDIDefs.h 源自开源 BasicSynth 库(Daniel R. Mitchell,GPL),定义了标准 MIDI 消息字节常量(见 MIDIDefs.h):

#define MAX_MIDI_CHNL 16
#define MIDI_EVTMSK  0xF0
#define MIDI_CHNMSK  0x0F
#define MIDI_MSGBIT  0x80

#define MIDI_NOTEOFF 0x80
#define MIDI_NOTEON  0x90
#define MIDI_KEYAT   0xA0
#define MIDI_CTLCHG  0xB0
#define MIDI_PRGCHG  0xC0
#define MIDI_CHNAT   0xD0
#define MIDI_PWCHG   0xE0
#define MIDI_SYSEX   0xF0
#define MIDI_META    0xFF

Source: MIDIDefs.h

这些常量被 midi_dec.c(SMF 文件解析)、midi_ctrl_api.c(电子琴事件构造)与 usb_midi.c(USB MIDI 端点数据)共同使用,保证全链路消息语义一致。控制器编号(CC)同样在此定义,如 MIDI_CTRL_VOL 0x07、MIDI_CTRL_PAN 0x0A、MIDI_CTRL_SUS_ON 0x40,合成引擎内部 MIDI_CTRL_VOL_CC 等枚举与之对应(见 midi_dec.h)。

Core Flow:乐谱播放端到端时序

sequenceDiagram
    participant App as toy_midi_app
    participant DA as decoder_api (MIDI_LST)
    participant MA as midi_api.c
    participant MD as midi_dec (解析/调度)
    participant MP as midi_play (合成器)
    participant Tone as SPI 音色表
    participant DAC as DAC

    App->>DA: 打开文件, 选择 MIDI_LST 解码器
    DA->>MA: midi_decode_api(p_file, &pdec, p_dp_buf)
    MA->>MA: 校验 midi_tone_tab / 分配 .midi_buf 工作区
    MA->>MD: ops->open + ops->format_check (SMF 头校验)
    MA->>MP: dec_confing(CMD_INIT_CONFIG, init_info)
    MP->>Tone: 读取音色表头部 (0xABCD 压缩格式判断)
    MA->>DAC: regist_dac_channel(&sound, kick_decoder)
    DAC-->>MP: kick 请求输出
    loop 每个采样块
        MP->>MD: 推进 srTicks / 取事件
        MD-->>MP: NoteOn/NoteOff/CC 事件
        MP->>Tone: 读取乐器采样 (定点重采样)
        MP->>MP: 多玩家混音 + 音量/衰减
        MP-->>DAC: PCM 输出到 cbuf_midi
    end
    App->>MA: 播放控制 (暂停/快进/停止)
    MA-->>App: 事件/错误码

时序要点:初始化阶段只做"配置下发"(CMD_INIT_CONFIG),真正的合成由 DAC 的 kick 驱动按块执行;midi_play 每块从事件 FIFO 取事件、从音色表取采样、混音输出。播放控制(暂停等)通过 MAD_ERROR_PAUSE(0x50)等错误码返回上层(见 midi_dec.h),与通用解码框架的暂停机制一致。

Usage Examples

示例 1:解码器注册与配置读取

MIDI 解码器通过标准 decoder_ops_t 接口注册,同时暴露两个供应用读取配置的访问器(见 midi_api.c):

const struct if_decoder_io midi_dec_io0 = {
    &dec_midi_hld,      //input跟output函数的第一个参数,解码器不做处理,直接回传,可以为NULL
    mp_input,
    mp_output,
};

MIDI_PLAY_CTRL_MODE *get_midi_mode(void)
{
    return &init_info.mode_info;
}

u32 *get_midi_switch_info(void)
{
    return &init_info.switch_info;
}

Source: midi_api.c

示例 2:应用定制初始化钩子

midi_init_info 被声明为 __attribute__((weak)) 的弱函数,默认空实现;应用层可以重定义它,在 CMD_INIT_CONFIG 下发前修改 init_info(如切换主旋律通道等)(见 midi_api.c):

__attribute__((weak))
void midi_init_info(MIDI_INIT_STRUCT *init_info)
{

}

Source: midi_api.c

示例 3:全局行为配置(app_config.c)

应用通过全局 const int 控制 MIDI 行为,这些变量被解码器模块以 extern 引用(见 app_config.c):

#if (DECODER_MIDI_EN | DECODER_MIDI_KEYBOARD_EN)
const int MAINTRACK_USE_CHN  = 0;    //0:用track号来区分  1:用channel号来区分。
const int MAX_DEC_PLAYER_CNT = 8;    //midi乐谱解码最大同时发声的key数,范围[1,31]
const int MAX_CTR_PLAYER_CNT = 8;    //midi琴最大同时发声的key数,范围[1,31]
const int NOTE_OFF_TRIGGER   = 0;    //midi琴note_off回调 1:time传0时,不会回调 0:time传0时,回调

Source: app_config.c

示例 4:工作模式分发(toy_main.c)

toy_main.c 根据 work_mode 进入不同的 MIDI 应用(见 toy_main.c):

#if defined(DECODER_MIDI_EN) && (DECODER_MIDI_EN)
        case TOY_MIDI:
            toy_midi_app();
            break;
#endif
#if defined(DECODER_MIDI_KEYBOARD_EN) && (DECODER_MIDI_KEYBOARD_EN)
        case TOY_MIDI_KEYBOARD:
            toy_midi_keyboard_app();
            break;
#endif

Source: toy_main.c

Configuration Options

MIDI 子系统的配置集中在两个层面:编译期宏开关(决定是否编译 MIDI 解码器/键盘功能)与运行期全局常量(在 app_config.c 中定义、被解码模块 extern 引用)。

编译期宏

宏作用域说明
DECODER_MIDI_EN全局使能 MIDI 乐谱解码器;为 0 时 midi_api.c 整体不编译(#if defined(DECODER_MIDI_EN) && (DECODER_MIDI_EN))
DECODER_MIDI_KEYBOARD_EN全局使能 MIDI 电子琴(键盘)模式

运行期全局常量(app_config.c)

选项类型默认值说明
MAINTRACK_USE_CHNconst int0主旋律识别方式:0 用 track 号区分,1 用 channel 号区分
MAX_DEC_PLAYER_CNTconst int8乐谱解码最大同时发声 key 数,范围 [1,31];决定合成缓冲区大小与 CPU 占用
MAX_CTR_PLAYER_CNTconst int8电子琴模式最大同时发声 key 数,范围 [1,31];同时是 MIDI_CTRL_OPEN 的硬上限
NOTE_OFF_TRIGGERconst int0note_off 回调行为:1 表示 time 传 0 时不回调;0 表示 time 传 0 时也回调

模块内常量(midi_dec.h)

常量值说明
INBUF_SIZE700MIDI 输入缓冲字节数
MAX_CHANNEL_NUM / MAX_TRACK_NUM16通道/音轨上限
MIDI_RESAMPLE_SHIFT13定点重采样精度(1<<13)
MIDI_SwitchLevel_CC64开关级 CC 阈值
MAX_GO_BACK8快退最大跨度(1<<8)
VOL_Norm_Bit12音量归一化位宽

API Reference

u32 midi_decode_api(void *p_file, void **ppdec, void *p_dp_buf)

MIDI 乐谱解码器标准入口,被解码框架经 MIDI_LST 调用。

参数:

  • p_file (void *):VFS 文件句柄
  • ppdec (void **):输出解码句柄(dec_obj *)
  • p_dp_buf (void *):解码参数缓冲(可为 NULL)

返回:

  • 0:成功
  • E_MIDI_NO_CFG:音色表未配置(midi_tone_tab == 0)
  • E_MIDI_DBUF:need_dcbuf_size() 超过静态工作区大小
  • E_MIDIFORMAT:format_check 失败

来源: midi_api.c

MIDI_PLAY_CTRL_MODE *get_midi_mode(void)

返回 init_info.mode_info 指针,供应用读取/修改播放控制模式(主旋律、开关等)。

来源: midi_api.c

u32 *get_midi_switch_info(void)

返回 init_info.switch_info 指针,供应用读取/修改开关信息。

来源: midi_api.c

void midi_init_info(MIDI_INIT_STRUCT *init_info)(弱函数)

默认为空实现;应用可重定义以在 CMD_INIT_CONFIG 下发前定制初始化参数。

来源: midi_api.c

static u32 MIDI_CTRL_OPEN(void *work_buf, void *dec_parm, void *parm)

合成器打开接口:解析音色表头(含 0xABCD 压缩格式判断)、初始化 16 通道 CC/弯音状态、分配玩家池与键位表、计算 srTicks 时间基准。

参数:

  • work_buf:MIDI_DECODE_VAR 工作区
  • dec_parm:MIDI_CTRL_PARM(含 tempo 等)
  • parm:MIDI_CONFIG_PARM(含 spi_pos 音色表地址、sample_rate、player_t)

返回: 0(成功)

来源: midi_play.c

Failure Modes、边界与并发

错误码与失败路径

场景返回/表现处理位置
音色表未配置E_MIDI_NO_CFGmidi_decode_api 入口,midi_tone_tab == 0
工作缓冲区不足E_MIDI_DBUFneed_dcbuf_size() > cal_buf_len,缓冲区随 MAX_DEC_PLAYER_CNT 变化
文件格式错误E_MIDIFORMATops->format_check 失败
采样率不支持ASSERT(0, "midi error sr !!!")midi_musicsr_to_cfgsr 未命中 9 档采样率表
解码/播放运行时错误MAD_ERROR_* 系列(0x05、0x40–0x48)MAD_ERROR_PAUSE = 0x50 表示暂停态;文件结束 MAD_ERROR_FILE_END = 0x40 等
格式部分支持FORMAT_OK_BUT_NO_SUPPORTmidi_dec.h 中格式检查三态之一

并发与中断安全

  • midi_decode_api 在清理 dec_midi_hld 时使用 local_irq_disable()/local_irq_enable() 保护,避免与 DAC 中断路径(kick_decoder)竞争写句柄(见 midi_api.c)。
  • 合成输出走环形缓冲 cbuf_midi(cbuffer_t),由 DAC 端按块消费,天然解耦实时合成与音频输出节奏。
  • 玩家池 play_key 初值 255 表示空槽,多通道键位互不冲突;MAX_PLAYER_CNTt 被 MAX_CTR_PLAYER_CNT 钳制,防止玩家数越界导致内存越写。

边界情况

  • 同时发声 key 数超过 MAX_DEC_PLAYER_CNT 时,音符叠加被丢弃(polyphony 饱和),这是"音质 vs 资源"的显式取舍。
  • MIDI_CTRL_OPEN 中若音色文件头既非 0xABCD 也无合法 instr_spi,instr_key_map 仍按默认偏移建立——音色文件损坏时合成器可能输出静音/异常音,但不崩溃。
  • NOTE_OFF_TRIGGER 控制 note_off 回调在 time == 0 时是否触发,影响电子琴 App 的事件驱动逻辑(见 app_config.c)。

Performance 与运维注意

  • CPU 开销与玩家数成正比:每个活跃音符都需要独立的采样插值(13 位定点重采样)与混音累加。MAX_DEC_PLAYER_CNT / MAX_CTR_PLAYER_CNT 建议按实际复音需求设置,默认 8 是性能与音质的平衡点;范围上限 31。
  • 内存全部静态分配:解码句柄、cbuffer、输出缓冲与合成工作区均放在 AT(.midi_buf) 放置段(见 midi_api.c),无运行时 malloc,可预测性好,但需在链接脚本中为该段预留足够空间。
  • 音色表读取为 SPI 访问:乐器采样从 SPI Flash 顺序读取,压缩格式(头部 0xABCD)可减少存储占用,代价是解压开销;选择音色文件格式时需权衡 Flash 容量与 CPU。
  • 采样率跟随 DAC:解码器按 dac_sr_read() 配置合成采样率,改变 DAC 采样率会触发 midi_musicsr_to_cfgsr 映射,务必使用表中 9 档标准采样率。

Extension Points

  1. midi_init_info(弱函数重定义):在 CMD_INIT_CONFIG 前定制 MIDI_INIT_STRUCT,是实现自定义主旋律通道、特殊开关逻辑的首选钩子。
  2. get_midi_mode() / get_midi_switch_info():应用可在解码过程中动态读取/修改播放控制模式与开关信息。
  3. midi_event.c 事件回调:解码/合成过程中的事件(如 note_on/note_off、mark 等)通过事件表 midi_evt[] 注册到 dec_midi_hld.event_tab,应用可挂接自定义回调实现歌词/按键灯等联动。
  4. 音色表格式:MIDI_CTRL_OPEN 对普通/压缩(0xABCD)两种音色文件头都支持,新增音色格式只需扩展头部解析分支。
  5. USB MIDI 输入:usb_midi.c 提供 USB 设备端 MIDI 端点,可将外部 MIDI 键盘事件桥接到 midi_ctrl_api(电子琴模式),实现"外部设备 + 内置合成器"的组合。

Related Links

  • 解码框架通用机制(decoder_api、MIDI_LST/MIDI_CTRL_LST 注册):decoder_api.c
  • 电子琴控制 API:sdk/app/bsp/common/decoder/list/midi_ctrl_api.c
  • 应用层乐谱播放/电子琴控制:sdk/app/src/voice_func/toy_midi/(toy_midi_ctrl.c、midi_config.c)
  • USB MIDI 设备实现:sdk/app/bsp/common/usb/device/usb_midi.c
  • 官方文档:《杰理 AD1x-45678 MIDI 应用说明文档》(doc/杰理AD1x-45678_MIDI应用说明文档.pdf)
Next
音频编码与录音