2025交互标牌 - 基于Seed XIAO ESP32S3 Sense实现Midi 音乐智能创编
该项目使用了Seed XIAO ESP32S3 Sense 开发板,实现了实现 MIDI 音乐智能创编的设计,它的主要功能为:通过哈希表建立 “音符 - 频率” 映射,将抽象音符转化为蜂鸣器可识别的振动频率,结合字符串解析模块拆解旋律指令。引入大语言模型,用户通过自然语言指令即可让 AI 生成符合规则的音符序列,经处理后由硬件播放。硬件上搭配定制音频解码方案与扩展底板,软件实现频率映射、歌曲解析、发声控制等功能,还集成在线音乐播放等附加功能,为嵌入式音乐场景提供实用解决方案。。
标签
小智
2025交互标牌
midi音乐
Seed XIAO ESP32S3 Sense
genvex
更新2025-08-18
21

2025交互标牌-基于Seed XIAO ESP32S3 Sense实现Midi音乐智能创编


回溯MIDI音乐的半个世纪,从1983年电子合成器首次用数字信号定义中央C=60”,到如今流媒体平台用01传输千万首旋律,音乐数字化始终在做一件事:让抽象的音符穿越硬件壁垒,自由流动。我们的项目,正是站在这场演进的延长线上——当早期工程师用5DIN接口连接键盘与合成器时,他们解决的是乐器间如何对话;而今天,我们尝试一种更为简单直观的方法来实现这个目标

灵感源自一个朴素的观察:MIDI协议的本质是音符指令的极简表达,就像乐谱里的豆芽菜符号,从巴赫的手稿到电子乐的音轨,核心从未变过——用最简洁的符号承载最丰富的韵律。如果把《小星星》的旋律压缩成“C4,C4,G4...”这样的字符序列,是不是能让最基础的蜂鸣器也听懂?早期MIDI状态字节+数据字节定义音符,我们用字符+逗号拆解节奏用哈希表建立音符-频率的映射法则。当蜂鸣器按照解析出的指令,以262Hz振动出第一个时,它延续的正是1980年代那批工程师的理想——让每一个硬件都能成为音乐的载体。

同时我们不满足预先编排好的曲目结合当前的高速发展的人工智能,我们使用小智聊天机器人的MCP组件搭建的智能交互工具接口正撕开这层边界:通过midi创编工作指令工具,大语言模型接过了作曲棒。只需说写一段清晨森林的旋律AI便会以科学音高记号法为谱,在数字世界即时创作:用E5模拟鸟鸣的清亮,G#3铺垫晨雾的朦胧,#的静默对应叶片落地的间隙,200 +音符的编排精准到每150BPM的呼吸感。这些字符流通过字符串解析模块拆分为独立音符,经频率映射转换为蜂鸣器可识别的振动频率(如A4=440Hz),最终让硬件唱出从未存在过的声线。

这种探索的价值,在于打破复杂设备才能做音乐的偏见。就像MIDI当年让家用电脑也能制作专业编曲,我们的项目证明:一块开发板、一段代码、一个蜂鸣器,足以搭建起音乐与技术的桥梁。那些被拆解的音符,既是给硬件的指令,也是给创造者的邀请——毕竟,音乐的本质从来不是昂贵的设备,而是对如何让声音有意义的永恒追问。

 

一、小智硬件构建

Seeed Studio XIAO ESP32S3 Sense系列是拇指大小(21x17.8mm左右)的小型开发板,搭载ESP32-S3R8处理器(Xtensa LX7双核,240MHz),支持2.4GHz Wi-Fi和蓝牙5.0。其中,Sense型号集成摄像头(OV2640/OV3660)、pdm数字麦克风和SD卡插槽,硬件需完成焊接排针、天线安装等准备,支持电池供电及多种低功耗模式,适用于可穿戴设备、智能语音和视觉AI等项目。Seeed Studio XIAO扩展底板是专为Seeed Studio XIAO系列设计的紧凑型扩展板,尺寸仅为树莓派4的一半,兼容XIAO SAMD21RP2040nRF52840型号。其核心优势在于丰富的外设(包括0.96英寸OLED显示屏、高精度RTC(±1.5S /天)、MicroSD卡槽、无源蜂鸣器等)、无需焊接的即插即用设计(支持Grove IICUART等接口),适用于快速原型制作、SWD调试、迷你尺寸项目等场景。

 鉴于XIAO ESP32S3 Sense已经在集成了pdm麦克风,只要再配备喇叭硬件就可以凑齐小智聊天机器人的硬件需求。目前市面上虽然已经有很多喇叭产品,但是没有跟XIAO ESP32S3 Sense完成融合的产品,为了提升产品的完整度美观需求,在嘉立创硬件平台上制作了一个以mx98357为核心的音频解码方案,配合xiao扩展版上的oled屏幕,搭建小智的硬件需求已全部满足。

 

 

midi音乐创想家软件构建

1频率映射:音符与物理频率的桥梁

蜂鸣器不懂“哆来咪”,只认“每秒振动多少次”。比如规定C4=262Hz(每秒抖262下),G4=392Hz(每秒抖392下),这样它才能发出不同的音高。把音乐里的 “哆来咪”(比如 “哆” 对应C4),翻译成蜂鸣器能看懂的 “振动速度”(单位Hz)。就像给钢琴按键贴标签,“中央C” 键对应 “按下去弦振动262/秒”,确保弹对键就出对音。通过标准化的频率映射表实现抽象音符到物理振动频率的一一对应实现了音符-频率的精准映射技术

技术细节:基于音乐声学理论,将A-G音符、升降调(#)和八度(0-9)的组合,精确映射为Hz值(如A4=440Hz为国际标准音),覆盖108个音符的完整频率体系。​

创新点:使用std::map构建哈希表,将音符查询复杂度降至O (1),确保实时播放时的高效转换,避免因查表延迟导致节奏错乱。

 

1映射表设计:noteFrequencies数组

数组存储音符名-频率键值对,覆盖从C0B9的所有音符(共108个),例如:

 

//频率映射表

static const TONE noteFrequencies[] = {

    {"C0", 16}, {"C#0", 17}, {"D0", 18}, {"D#0", 19}, {"E0", 21}, {"F0", 22},

    {"F#0", 23}, {"G0", 24}, {"G#0", 26}, {"A0", 28}, {"A#0", 29}, {"B0", 31},

    {"C1", 33}, {"C#1", 35}, {"D1", 37}, {"D#1", 39}, {"E1", 41}, {"F1", 44},

    {"F#1", 46}, {"G1", 49}, {"G#1", 52}, {"A1", 55}, {"A#1", 58}, {"B1", 62},

    {"C2", 65}, {"C#2", 69}, {"D2", 73}, {"D#2", 78}, {"E2", 82}, {"F2", 87},

    {"F#2", 92}, {"G2", 98}, {"G#2", 104}, {"A2", 110}, {"A#2", 117}, {"B2", 123},

    {"C3", 131}, {"C#3", 139}, {"D3", 147}, {"D#3", 156}, {"E3", 165}, {"F3", 175},

    {"F#3", 185}, {"G3", 196}, {"G#3", 208}, {"A3", 220}, {"A#3", 233}, {"B3", 247},

    {"C4", 262}, {"C#4", 277}, {"D4", 294}, {"D#4", 311}, {"E4", 330}, {"F4", 349},

    {"F#4", 370}, {"G4", 392}, {"G#4", 415}, {"A4", 440}, {"A#4", 466}, {"B4", 494},

    {"C5", 523}, {"C#5", 554}, {"D5", 587}, {"D#5", 622}, {"E5", 659}, {"F5", 698},

    {"F#5", 740}, {"G5", 784}, {"G#5", 831}, {"A5", 880}, {"A#5", 932}, {"B5", 988},

    {"C6", 1047}, {"C#6", 1109}, {"D6", 1175}, {"D#6", 1245}, {"E6", 1319}, {"F6", 1397},

    {"F#6", 1480}, {"G6", 1568}, {"G#6", 1661}, {"A6", 1760}, {"A#6", 1865}, {"B6", 1976},

    {"C7", 2093}, {"C#7", 2217}, {"D7", 2349}, {"D#7", 2489}, {"E7", 2637}, {"F7", 2794},

    {"F#7", 2960}, {"G7", 3136}, {"G#7", 3322}, {"A7", 3520}, {"A#7", 3729}, {"B7", 3951},

    {"C8", 4186}, {"C#8", 4435}, {"D8", 4699}, {"D#8", 4978}, {"E8", 5274}, {"F8", 5588},

    {"F#8", 5920}, {"G8", 6272}, {"G#8", 6645}, {"A8", 7040}, {"A#8", 7459}, {"B8", 7902},

    {"C9", 8372}, {"C#9", 8870}, {"D9", 9397}, {"D#9", 9956}, {"E9", 10548}, {"F9", 11175},

    {"F#9", 11840}, {"G9", 12544}, {"G#9", 13290}, {"A9", 14080}, {"A#9", 14917}, {"B9", 15804},

    {"#", 0} //静音符号

};

 音符命名规则:字母(A-G+变调符(#表示升调)+八度(0-9),如A#5表示小字五组升A”

 频率值基于音乐理论计算(每高八度频率翻倍,如A4=440HzA5=880Hz)。

2快速查询:tonesMap哈希表

构造函数中将数组转换为std::map<std::string, int>,实现O (1)复杂度的音符频率查询:

MidiPlayer::MidiPlayer(gpio_num_t pin) : pinNumber(pin) {

    for (int i = 0; i < NUM_NOTES; ++i) {

        tonesMap[noteFrequencies[i].note] = noteFrequencies[i].freq;

    }

}

 例如:查询tonesMap["A4"]直接返回440Hz)。

3频率获取:getFrequency函数

对外提供接口,输入音符名返回频率,支持静音符号#):

 

2歌曲解析:字符串到音符的拆解

预置音乐数据以逗号分隔的字符串形式存储。每个字符串代表一首完整的乐曲,由一系列音符组成。假设蜂鸣器要唱《小星星》,“乐谱” 是一串密密麻麻的字符:C4,C4,G4,G4,A4,A4,G4..."。这串字符对蜂鸣器来说,就像一整页没标点的歌词 —— 它不知道哪里该停顿,哪个字是开头。字符串到音符拆解 的作用,就是按逗号把长字符串切成一段段短的,比如从"C4,C4,G4"里拆出"C4""C4""G4"。​这么做的目的很简单:蜂鸣器 一次只能处理一个音符蜂鸣器也得把长字符串拆成单个音符,才能一个接一个地播放,不然就会“卡壳” 或者把所有音混在一起变成噪音。​比如拆解前是一整串 “哆哆索索拉...,拆解后变成清晰的 “哆、哆、索、索、拉...”,每个音符单独排队,等着被 “唱” 出来。这一步就像给乐谱 “断句”,是让音乐能按顺序播放的基础。

1 歌曲数据格式

项目中定义的歌曲(如my_people_my_country twinkle_star)均为逗号分隔的音符字符串,例如:

const char* twinkle_star = "C4,C4,G4,G4,A4,A4,G4,F4,F4,E4,E4,D4,D4,C4";

 每个元素是音符(如C4)或静音(#),逗号分隔表示音符的先后顺序。

2核心解析函数:parseNextNote

从字符串中逐个提取音符,处理连续逗号、非法字符等边缘情况:

 

bool parseNextNote(const char** pos, char* note, int maxLen) {

    if (!pos || !*pos || **pos == '\0') return false; //空字符串直接返回

    int len = 0;

    //逐个字符读取,直到遇到逗号或字符串结束

    while (** pos && **pos != ',' && len < maxLen - 1) {

        note[len++] = *(*pos)++;

    }

    note[len] = '\0'; //手动补全字符串结束符

    if (** pos == ',') (*pos)++; //跳过逗号,准备下一次解析

    return len > 0; //确保提取到有效音符

}

3解析播放的衔接:playNote函数

将解析出的音符转换为频率,并调用发声控制函数:

void playNote(const char *note) {

    int freq = getFrequency(note); // 1.查频率映射表

    static int lastFreq = 0;

    if (freq != lastFreq) { // 2.频率变化时才更新(优化性能)

        lastFreq = freq;

        if (freq > 0) startTone(freq); // 3.发声

        else stopTone(); //静音或无效音符

    }

}

3发声控制:硬件驱动的核心实现

发声控制是项目与硬件交互的最后一公里,通过ESP32LEDC模块(LED控制器)实现蜂鸣器的频率输出,直接决定音符能否被物理发声。

1硬件基础:LEDC模块的作用

ESP32LEDC原本用于控制LED亮度,但其核心功能是生成特定频率和占空比的方波,恰好可用于驱动蜂鸣器(蜂鸣器通过方波频率振动发声)。

 核心配置

 定时器(LEDC_TIMER):负责产生基准时钟,决定方波的频率范围。

 通道(LEDC_CHANNEL):绑定具体GPIO引脚,输出方波信号。

2关键函数:startTonestopTone

 startTone(int freq):启动发声

 

void startTone(int freq) {

    ledc_set_freq(LEDC_MODE, LEDC_TIMER, freq); // 1.设置定时器频率(即音符频率)

    ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 500); // 2.设置占空比(500/1023 ≈ 50%,保证音量适中)

    ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);  // 3.生效配置,蜂鸣器开始振动

}

 频率freq直接对应音符(如A4=440Hz),占空比控制振动幅度(音量)。

 stopTone():停止发声

 

void stopTone() {

    ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0); //占空比设为0,方波消失

    ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);

}

3初始化准备:init()函数

在发声前需初始化硬件,确保LEDC模块和GPIO引脚就绪:

 

void init() {

    gpio_set_direction(pinNumber, GPIO_MODE_OUTPUT); //配置引脚为输出

    //初始化定时器(设置频率范围、分辨率等)

    ledc_timer_config(&timer_conf);

    //绑定通道与引脚,配置中断、占空比初始值(0,默认不发声)

    ledc_channel_config(&channel_conf);

}

4任务调度:节奏控制与并行运行

音乐播放需要按时间顺序连续执行(如每秒播放2个音符),且不能阻塞其他程序逻辑,因此通过FreeRTOS任务实现独立调度。这样主程序可以保持正常运行,歌曲可以在后台连续播放。


1任务核心:playTaskFunction

这是一个独立的FreeRTOS任务,负责按节奏解析音符、控制播放,流程如下:

 

void playTaskFunction(void* parameter) {

    MidiPlayer* player = static_cast<MidiPlayer*>(parameter);

    const char* songPos = player->currentSong; //初始位置:歌曲开头

    uint64_t lastTick = esp_timer_get_time() / 1000; //记录上一次播放时间(ms

    while (player->taskRunning && !player->stopped) { //循环条件:任务运行且未停止

        if (player->paused) { //暂停状态:等待恢复

            vTaskDelay(pdMS_TO_TICKS(10));

            lastTick = esp_timer_get_time() / 1000; //更新时间戳,避免暂停后节奏错乱

            continue;

        }

        //检查是否到下一个音符的播放时间(按tempo节奏)

        uint64_t currentTime = esp_timer_get_time() / 1000;

        if (currentTime - lastTick >= (uint64_t)player->taskTempo) {

            char note[4];

            if (player->parseNextNote(&songPos, note, sizeof(note))) { // 1.解析下一个音符

                player->playNote(note); // 2.播放音符

            } else { //解析完毕(歌曲结束)

                if (player->taskLooping) songPos = player->currentSong; //循环播放:重置位置

                else { //非循环:停止播放

                    player->stopped = true;

                    player->stopTone();

                    break;

                }

            }

            lastTick = currentTime; //更新时间戳

        }

        vTaskDelay(pdMS_TO_TICKS(20)); //让出CPU,避免占用过多资源

    }

    //任务结束:清理资源

    player->taskRunning = false;

    player->stopTone();

    vTaskDelete(NULL);

}

2节奏控制:tempotaskTempo

 tempoBPMBeats Per Minute,每分钟节拍数),例如tempo=120表示每分钟播放120个音符。

 taskTempo:每个音符的播放间隔(ms),由tempo换算而来:

 

taskTempo = 60000 / tempo; // 60000ms = 1分钟

例如:tempo=120 → taskTempo=500ms(每个音符播放500ms)。

3任务管理:play / stopPlayback / pause

 启动任务(play):

void play(int tempo, uint8_t looping, const char *song) {

    setSong(song); //设置歌曲

    taskTempo = 60000 / tempo; //计算间隔

    taskLooping = looping; //是否循环

    xTaskCreate(playTaskFunction, "MidiPlayTask", 4096, this, 5, &playTaskHandle); //创建任务

}

5智能创编:核心交互与播放全流程控制

当我们明确了曲目编排的规则——以科学音高记号法(如C4 A#5)为基础,用英文逗号分隔音符,通过#表示静音,且需包含200个以上音符构建完整旋律后,将这一任务交给大语言模型,本质上是搭建人类意图→AI生成硬件执行的智能闭环。而实现这一闭环的核心,正是通过小智McpServer注册的工具接口与播放控制逻辑的深度联动。

大语言模型的接入需依托小智的mcpToools工具构建双向交互通道:

 指令输入层:用户通过语音或文本向大语言模型发送创作需求(如写一段欢快的生日旋律),模型基于对自然语言的理解,转化为符合编排规则的音符字符串(例如C4,D4,E4,C4,C4,D4,E4,C4,E4,F4,G4,#,G4...)。

 格式约束层:工具描述中严格定义的规则(科学音高记号法、英文逗号分隔、200 +音符等),成为大语言模型生成内容的语法规范,确保输出的字符串可被MidiPlayer直接解析这相当于给AI配备了音乐编码手册,避免生成无法识别的乱码旋律

 执行链路层:模型生成的音符字符串通过properties["music"]传入回调函数后,MidiPlayer立即启动解析与播放流程:printSongNotes负责校验并拆解字符串为独立音符(如从"C4,A#5"中提取C4A#5),play(150, 0, ...)则通过频率映射将音符转换为蜂鸣器的振动参数(如A4对应440Hz),最终让AI创作的旋律从抽象符号转化为物理声音。

  midiPlayer_.init();

    midiPlayer_.play(500, 0, MidiPlayer::twinkle_star); // 500ms间隔播放

    auto &mcp_server = McpServer::GetInstance();

    mcp_server.AddTool("self.Midi.createMusic",

                       "你是音乐大师,按照用户要求创制midi音乐曲目,把曲目返回给用户,使用科学音高记号法,用逗号分割音符,如'C4', 'A4',每个八度从C开始到B结束,包含半音(升降号):C#D#F#G#A#,使用'#'表示静音(频率为0)"

                       "曲目完整准确,音符200个以上,不得使用中文逗号"

                       "例如小星星是'C4,C4,G4,G4,A4,A4,G4,F4,F4,E4,E4,D4,D4,C4'",

                       PropertyList({Property("music", kPropertyTypeString)}), [this](const PropertyList &properties) -> ReturnValue

                       {

            music_ = properties["music"].value<std::string>();

            ESP_LOGI(TAG, "创编的曲目内容为: %s", music_.c_str());

            this->midiPlayer_.printSongNotes(music_.c_str());

            this->midiPlayer_.play(150, 0, music_.c_str()); //使用成员变量

            return true; });

    mcp_server.AddTool("self.Midi.playMidi",

                       "播放Nmidi曲目,把曲目编号返回给用户,如第1首返回'1'",

                       PropertyList({Property("index", kPropertyTypeInteger)}), [this](const PropertyList &properties) -> ReturnValue

                       {

            uint8_t  index_ = properties["index"].value<int>();

            ESP_LOGI(TAG, "播放第%d首曲目", index_);

            this->midiPlayer_.setMusicIndex(index_);

            this->midiPlayer_.musicupdate();

            return true; });

    mcp_server.AddTool("self.Midi.stop",

                       "停止播放midi曲目",

                       PropertyList(), [this](const PropertyList &properties) -> ReturnValue

                       {

            this->midiPlayer_.stopPlayback() ;                        

                        return true; });

 

三、总结

本项目结合小智聊天机器人mcp机制及乐鑫IDF编程的强大功能与灵活性兼具特性,深度挖掘xiaoS3 sense的潜能,除了midi音乐创编功能,同时也集成了其他有趣的功能,如在线音乐播放,B站粉丝功能查询,每日中英文鸡汤,复读机等功能,让该设备使用起来更加有趣。项目通过将音符拆解为字符序列,用哈希表建立 “音符-频率” 映射,让基础硬件能理解并播放音乐。同时,结合人工智能技术,引入大语言模型接过作曲棒,实现了从用户自然语言指令到硬件实时播放全新旋律的智能闭环。项目的核心功能,实现了多种嵌入式音乐播放场景,结合硬件交互、传感器数据和任务调度,拓展了蜂鸣器的音乐控制能力,具有良好的可扩展性和实用性,可以轻松集成到各种嵌入式应用中。例如互式音乐控制、传感器联动报警、节拍器功能、闹钟应用、游戏音效系统项目。


 

1.智能蜂鸣器音乐播放器

#include "MidiPlayer.h"

//创建一个连接蜂鸣器的音乐播放器

MidiPlayer player(GPIO_NUM_18); //蜂鸣器连接到GPIO18

void app_main() {

    player.init(); //初始化播放器    

    //播放预定义歌曲

    player.musicupdate(); //播放默认歌曲    

    //或者播放自定义歌曲

    const char* birthday_song = "C4,C4,D4,C4,F4,E4,C4,C4,D4,C4,G4,F4,C5,C5,A4,F4,E4,D4";

    player.play(200, 1, birthday_song); //以200ms间隔播放,循环    

    vTaskDelay(pdMS_TO_TICKS(5000)); //播放5秒

    player.stopPlayback(); //停止播放

}



2.按钮切换歌曲播放器

cpp

#include "MidiPlayer.h"

#include "driver/gpio.h" 

#define BUTTON_PIN GPIO_NUM_0

MidiPlayer player(GPIO_NUM_18);

static bool button_pressed = false;

//按钮中断处理函数

static void IRAM_ATTR button_isr_handler(void* arg) {

    button_pressed = true;

}

void app_main() {

    //初始化播放器

    player.init();    

    //配置按钮

    gpio_config_t io_conf = {};

    io_conf.intr_type = GPIO_INTR_NEGEDGE;

    io_conf.pin_bit_mask = (1ULL << BUTTON_PIN);

    io_conf.mode = GPIO_MODE_INPUT;

    io_conf.pull_up_en = GPIO_PULLUP_ENABLE;

    gpio_config(&io_conf);    

    //注册中断服务

    gpio_install_isr_service(0);

    gpio_isr_handler_add(BUTTON_PIN, button_isr_handler, NULL);    

    //播放默认歌曲

    player.musicupdate();    

    while(1) {

        if(button_pressed) {

            player.setMusicIndex();  //切换到下一首歌曲

            player.musicupdate();    //播放新歌曲

            button_pressed = false;

            vTaskDelay(pdMS_TO_TICKS(500)); //防抖动

        }

        vTaskDelay(pdMS_TO_TICKS(10));

    }

}


3.温度警报音乐播放器

#include "MidiPlayer.h"

#include "driver/temp_sensor.h"

MidiPlayer player(GPIO_NUM_18);

temp_sensor_config_t temp_sensor = TSENS_CONFIG_DEFAULT();

void app_main() {

    player.init();    

    //初始化温度传感器

    temp_sensor_install(&temp_sensor);

    temp_sensor_start();    

    float temperature;    

    while(1) {

        temp_sensor_read_celsius(&temperature);        

        if(temperature > 30.0) {

            //温度过高,播放警报音乐

            const char* alert_tone = "A5,A5,A5,F5";

            player.play(100, 1, alert_tone); //快速重复警报音

        } else if(temperature > 25.0) {

            //温度较高,播放提醒音乐

            const char* warning_tone = "C5,E5,G5,C6";

            player.play(300, 0, warning_tone); //一次提醒音

        } else {

            //正常温度,停止播放

            if(!player.isStop()) {

                player.stopPlayback();

            }

        }        

        vTaskDelay(pdMS_TO_TICKS(1000)); //每秒检查一次温度

    }

}


4.简单的音乐节拍器

cpp

#include "MidiPlayer.h"
MidiPlayer player(GPIO_NUM_18);
void app_main() {
    player.init();    
    //节拍器:每分钟120拍(每拍500ms)
    const char* metronome = "A5,#,A5,#"; //声音-静音-声音-静音循环    
    while(1) {
        player.play(500, 1, metronome); // 500ms间隔播放        
        //用户可以通过按键或其他方式调整节拍速度
        vTaskDelay(pdMS_TO_TICKS(10000)); //播放10秒后可以调整        
        //更改节拍速度至每分钟60拍(每拍1000ms)
        player.setTempo(1000);
        vTaskDelay(pdMS_TO_TICKS(10000)); //再播放10秒        
        //恢复到原来的速度
        player.setTempo(500);
    }
}
5.多功能闹钟
cpp
#include "MidiPlayer.h"
#include "time.h"
MidiPlayer player(GPIO_NUM_18);
void app_main() {
    player.init();    
    //设定闹钟时间
    struct tm alarm_time = {0};
    alarm_time.tm_hour = 7;  //早上7点
    alarm_time.tm_min = 0;
    alarm_time.tm_sec = 0;    
    time_t alarm_timestamp = mktime(&alarm_time);    
    while(1) {
        time_t now;
        time(&now);
        struct tm timeinfo;
        localtime_r(&now, &timeinfo);        
        //检查是否到达闹钟时间
        if(now >= alarm_timestamp &&
           timeinfo.tm_hour == alarm_time.tm_hour &&
           timeinfo.tm_min == alarm_time.tm_min &&
           timeinfo.tm_sec < 10) { //前10秒播放            
            //播放闹钟铃声
            player.play(200, 1, "C5,E5,G5,C6,E6,G5,E5,C5"); //欢快的旋律            
            //持续播放直到用户按下按钮或1分钟后自动停止
            vTaskDelay(pdMS_TO_TICKS(60000));
            player.stopPlayback();            
            //更新下一次闹钟时间(第二天)
            alarm_timestamp += 24 * 3600;
        }       
        vTaskDelay(pdMS_TO_TICKS(1000)); //每秒检查一次时间
    }
}

6.游戏音效播放器

cpp

#include "MidiPlayer.h"
MidiPlayer player(GPIO_NUM_18);
//定义游戏音效
const char* coin_sound = "C6,E6,G6,C7";      //拾取金币
const char* jump_sound = "C4,G4,C5,G5";      //跳跃音效
const char* game_over_sound = "C3,G2,C2,G1"; //游戏结束
void play_coin_sound() {
    player.play(50, 0, coin_sound);
}
void play_jump_sound() {
    player.play(100, 0, jump_sound);
}
void play_game_over_sound() {
    player.play(200, 0, game_over_sound);
}
void app_main() {
    player.init();    
    //模拟游戏场景
    while(1) {
        printf("Press any key to simulate game events:\n");
        getchar();        
        int event = rand() % 3;
        switch(event) {
            case 0:
                printf("Player got a coin!\n");
                play_coin_sound();
                break;
            case 1:
                printf("Player jumped!\n");
                play_jump_sound();
                break;
            case 2:
                printf("Game over!\n");
                play_game_over_sound();
                break;
        }        
        vTaskDelay(pdMS_TO_TICKS(2000)); // 2秒间隔
    }
}
 

附件:

项目源码:https://gitee.com/genvex/xiaozhi-esp32-music/tree/main/main/boards/bread-compact-wifi

注意: 该文件夹里有源码及固件,如需自行编译需要克隆完整项目。


小智编译指导:

##按键配置
* Button(D1,扩展底座按键):短按-打断/唤醒、开机配置网络
##编译配置命令
**配置编译目标为ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开menuconfig:**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type ->面包板新版接线(WiFi)
**修改psram配置:**
```
Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM
```
**修改flash大小:**
```
Serial flasher config -> Flash size -> 8 MB
OLED Type
SSD1306,分辨率128*64
```
**修改分区表:**
```
Partition Table -> Custom partition CSV file -> partitions/v1/8m.csv
```
**编译:**
```bash
idf.py build
```
**烧录:**
```bash
idf.py flash
```

团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号