2025交互标牌-基于Seed XIAO ESP32S3 Sense实现Midi音乐智能创编
回溯MIDI音乐的半个世纪,从1983年电子合成器首次用数字信号定义“中央C=60”,到如今流媒体平台用0和1传输千万首旋律,音乐数字化始终在做一件事:让抽象的音符穿越硬件壁垒,自由流动。我们的项目,正是站在这场演进的延长线上——当早期工程师用5针DIN接口连接键盘与合成器时,他们解决的是“乐器间如何对话”;而今天,我们尝试一种更为简单直观的方法来实现这个目标。
灵感源自一个朴素的观察: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 SAMD21、RP2040和nRF52840型号。其核心优势在于丰富的外设(包括0.96英寸OLED显示屏、高精度RTC(±1.5S /天)、MicroSD卡槽、无源蜂鸣器等)、无需焊接的即插即用设计(支持Grove IIC、UART等接口),适用于快速原型制作、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数组
数组存储“音符名-频率”键值对,覆盖从C0到B9的所有音符(共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=440Hz,A5=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"]直接返回440(Hz)。
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)发声控制:硬件驱动的核心实现
发声控制是项目与硬件交互的“最后一公里”,通过ESP32的LEDC模块(LED控制器)实现蜂鸣器的频率输出,直接决定音符能否被物理发声。
1、硬件基础:LEDC模块的作用
ESP32的LEDC原本用于控制LED亮度,但其核心功能是生成特定频率和占空比的方波,恰好可用于驱动蜂鸣器(蜂鸣器通过方波频率振动发声)。
• 核心配置:
◦ 定时器(LEDC_TIMER):负责产生基准时钟,决定方波的频率范围。
◦ 通道(LEDC_CHANNEL):绑定具体GPIO引脚,输出方波信号。
2、关键函数:startTone与stopTone
• 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、节奏控制:tempo与taskTempo
• tempo:BPM(Beats 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"中提取C4和A#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", "播放N首midi曲目,把曲目编号返回给用户,如第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
```