keyboard

Example of the component espressif/esp_midi v1.0.0
# MIDI 电子琴 example

- [English](./README.md)

## 例程简介

本例程演示了如何实时处理 MIDI 消息并控制音频播放,实现了一个简单的电子琴功能。使用支持矩阵键盘或 USB 键盘作为电子琴的琴键输入设备,可以通过 menuconfig 进行配置。两种键盘对应音符的映射均可以在代码中进行修改。

若使用 USB 键盘,默认的按键对应音符示意图如下:

```plain
    ┌───┬───┐   ┌───┬───┬───┐                               
    │ 2 │ 3 │   │ 5 │ 6 │ 7 │                               
  ┌─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┐                             
  │ Q │ W │ E │ R │ T │ Y │ U │                             
  └───┴───┴───┴───┴───┴───┴───┘ ┌───┬───┐   ┌───┬───┬───┐   
    0 1 2 3 4 ...           11  │ F │ G │   │ J │ K │ L │   
                              ┌─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┐ 
                              │ C │ V │ B │ N │ M │ < │ > │ 
                              └───┴───┴───┴───┴───┴───┴───┘ 
                               12 13 ...                23  
```

若使用 5x5 的矩阵键盘,默认的按键对应音符示意图如下:

```plain
         ┌──┐   ┌──┐          ┌──┐    
         │ 1│   │ 3│          │ 6│    
     ┌───┼──┼───┼──┼───┐  ┌───┼──┼───┐
     │ 0 │  │ 2 │  │ 4 │  │ 5 │  │ 7 │
     └───┘  └───┘  └───┘  └───┘  └───┘
  ┌──┐   ┌──┐          ┌──┐   ┌──┐    
  │ 8│   │10│          │13│   │15│    
  └──┼───┼──┼───┐  ┌───┼──┼───┼──┼───┐
     │ 9 │  │ 11│  │ 12│  │ 14│  │ 16│
     └───┘  └───┘  └───┘  └───┘  └───┘
         ┌──┐   ┌──┐   ┌──┐           
         │18│   │20│   │22│           
     ┌───┼──┼───┼──┼───┼──┼───┐  ┌───┐
     │ 17│  │ 19│  │ 21│  │ 23│  │ 24│
     └───┘  └───┘  └───┘  └───┘  └───┘
```

系统框图如下:

```plain
                                                                                                  
                             ┌──────────────────────────┐                                         
                             │         ESP32-S3         │                                         
                             │                          │                                         
   ┌────────────────────────►│GPIO6               GPIO48├──►                                      
   │                         │                          │                                         
   │   ┌────────────────────►│GPIO10              GPIO1 ├──► power control                        
   │   │                     │                          │                                         
   │   │   ┌────────────────►│GPIO11              GPIO3 ├──►                                      
   │   │   │                 │                          │                                         
   │   │   │   ┌────────────►│GPIO12                    │    ┌───────────┐                        
   │   │   │   │             │                          │    │   codec   │                        
   │   │   │   │   ┌────────►│GPIO13                    │    │           │                        
   │   │   │   │   │         │                    GPIO16├───►│MCLK       │   ┌───────┐  ┌───────┐ 
                             │                          │    │           │   │  PA   │  │  SPK  │ 
   │   │   │   │   │         │                    GPIO45├───►│WS         ├──►│       ├─►│       │ 
 ──┼───┼───┼───┼───┼── ─────►│GPIO39                    │    │           │   │       │  │       │ 
   │   │   │   │   │         │                    GPIO9 ├───►│BCLK       │ ┌►│EN     │  │       │ 
 ──┼───┼───┼───┼───┼── ─────►│GPIO40                    │    │           │ │ │       │  │       │ 
   │   │   │   │   │         │                    GPIO8 ├───►│DIN        │ │ └───────┘  └───────┘ 
 ──┼───┼───┼───┼───┼── ─────►│GPIO41                    │    │           │ │                      
   │   │   │   │   │         │                          │    └───────────┘ │                      
 ──┼───┼───┼───┼───┼── ─────►│GPIO42                    │                  │                      
   │   │   │   │   │         │                    GPIO47│──────────────────┘                      
 ──┼───┼───┼───┼───┼── ─────►│GPIO46                    │    ┌───────────┐                        
   │   │   │   │   │         │                          │    │   knob    │                        
                             │                          │    │           │                        
    matrix keyboard    ┌────►│GPIO35              GPIO37│◄───┤A          │                        
                       │     │                          │    │           │                        
         ┌───────────┐ │ ┌──►│GPIO34              GPIO38│◄───┤B          │                        
         │   knob    │ │ │   │                          │    │           │                        
         │           │ │ │ ┌►│GPIO33              GPIO36│◄───┤D          │                        
         │          A├─┘ │ │ │                          │    │           │                        
         │           │   │ │ └──────────────────────────┘    └───────────┘                        
         │          B├───┘ │                                                                      
         │           │     │                                                                      
         │          D├─────┘                                                                      
         │           │                                                                            
         └───────────┘                                                                            
                                                                                                  
```

本例程默认使用两个旋转编码器作为控制旋钮,可以自定义旋转和按下时执行的操作。默认的旋钮功能如下:

| 按键           | 功能               |
|--------------|------------------|
| 旋钮A - 按住向左滚动 | 音域减少一度(1 个半音)    |
| 旋钮A - 向左滚动   | 音域减少一个八度(12 个半音) |
| 旋钮A - 按住向右滚动 | 音域增加一度(1 个半音)    |
| 旋钮A - 向右滚动   | 音域增加一个八度(12 个半音) |
| 旋钮B - 按住向左滚动 | 切换乐器(序号 -1)      |
| 旋钮B - 向左滚动   | 播放速度 -0.25       |
| 旋钮B - 按住向右滚动 | 切换乐器(序号 +1)      |
| 旋钮B - 向右滚动   | 播放速度 +0.25       |

## 预备知识

播放所需的音频文件存储在 Flash 的 `soundfile` 分区中。

为了简化烧录操作,本例程使用 `merge_wav_tool_keynote.py` 脚本将多个音频文件合并为一个音源文件,并生成对应的索引文件。

### 生成音源文件

1. 将音频文件放入某个文件夹下,如 `resource_path`。

    在本例程中,音频文件名格式为 `instr/drum_name_index_note.wav`,如 `instr_guitar_025_038.wav`。其中每个字段的含义如下:

    | 名称         | 含义      | 类型  | 最小值 | 最大值 |
    |------------|---------|-----|-----|-----|
    | instr/drum | 乐器或打击乐器 | 字符串 | -   | -   |
    | name       | 乐器名称    | 字符串 | -   | -   |
    | index      | 乐器序号    | 整形  | 0   | 127 |
    | note       | 音符序号    | 整形  | 0   | 127 |

    注意:

    - 文件名格式须严格遵循上述格式,否则脚本无法正确解析音频文件。
    - 所有音频文件需为 WAV 格式,且采样率和声道数须一致。本例程使用的音频文件均为 16 位单声道采样率为 16000 的 WAV 格式音频文件。
    - 若修改音频文件名格式,需同时修改 `merge_wav_tool_keynote.py` 脚本和 `main/load_sound_lib/src/esp_midi_flash_loader.c` 中的 `esp_midi_flash_loader_noteon` 函数。

2. 执行 `python3 merge_wav_tool_keynote.py -path resource_path` 指令,脚本将在音频文件夹下生成 `midi_sound_files.bin` 音源文件和 `midi_sound_files.h` 索引文件。
3. 将 `midi_sound_files.bin` 和 `midi_sound_files.h` 文件放入 `main/load_sound_lib` 目录下。

### 烧录音源文件

1. 本例程的音源文件存放位置为 Flash 中的 `soundfile` 分区,分区地址和大小在 `partitions.csv` 分区表中配置如下,用户可以根据自己的项目 flash 分区灵活配置地址和分区大小。本例程默认使用的音频文件大小约 15.4MB,板载 Flash 需要至少 16MB 存放全部固件和数据。

    ```code
    soundfile,data, 0xff,    0x110000, 15296K,
    ```

2. 执行以下指令将 `main/load_sound_lib` 目录下的 `midi_sound_files.bin` 文件烧录到 `soundfile` 分区。

    以 `esp32s3` 芯片,串口 `/dev/ttyACM0` 为例,烧录指令如下:

    ```bash
    esptool.py --chip esp32s3 --port /dev/ttyACM0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x110000 ./main/midi_sound_files/midi_sound_files.bin
    ```

## 工程配置

开发板的管脚配置可以在 `main/app_board.h` 中进行修改。

本例程默认使用矩阵键盘作为输入设备,用户可以通过 menuconfig 进行配置。若使用 USB 键盘,需在 menuconfig 中选择 `USB Keyboard` 作为输入设备。

```plain
menuconfig > Keyboard Example Configuration > Input Key Selection > USB Keyboard
```

## 编译和下载

1. 执行以下命令选择使用的芯片型号,例程依赖的组件将自动下载到 `managed_components` 文件夹中。

    ```bash
    idf.py set-target esp32s3
    ```

2. 本例程使用 `esp_board_manager` 管理开发板硬件资源,需要将  `esp_board_manager` 的路径添加进环境变量中。若使用 Ubuntu 或 Mac 系统,请执行以下命令,其他系统请参考 [esp_board_manager 文档](https://github.com/espressif/esp-gmf/blob/main/packages/esp_board_manager/README_CN.md#1-%E8%AE%BE%E7%BD%AE-idf-action-%E6%89%A9%E5%B1%95):

    ```bash
    export IDF_EXTRA_ACTIONS_PATH=./managed_components/espressif__esp_board_manager
    ```

3. 生成板级配置文件。默认的硬件配置文件存放在 `components/midi_keyboard/` 目录下。执行以下指令后,将自动生成 `gen_bmgr_codes` 配置代码,若修改了硬件配置文件,需重新执行此指令。

    ```bash
    idf.py gen-bmgr-config -b midi_keyboard -c components/midi_keyboard
    ```

4. 编译工程并烧录到开发板上,然后运行 monitor 工具来查看串口输出(替换 PORT 为端口名称):

    ```bash
    idf.py -p PORT flash monitor
    ```

5. 本例程还需烧录音源文件到 `partitions.csv` 分区表中指定的 `soundfile` 分区,请参考预备知识章节操作

使用 ``Ctrl-]`` 退出调试界面。

有关配置和使用 ESP-IDF 生成项目的完整步骤,请参阅 [《ESP-IDF 编程指南》](https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4.1/esp32s3/index.html)。

## FAQ

1. 我的 Flash 不够大,无法烧录音源文件怎么办?

    若您的 Flash 不足 16MB,则无法烧录本例程预置的音源文件。您可以参考[预备知识](#预备知识)章节和[如何自定义音源文件](#如何自定义音源文件)自行准备音源文件并烧录到 Flash 中。

2. 如何自定义音源文件?<a id="如何自定义音源文件"></a>

    本例程默认将音源文件烧录到 Flash 中的 `soundfile` 分区。音源文件由多个参数相同的 WAV 音频文件合并而成。您可以自定义音源文件,需要修改以下几个文件:

    - `merge_wav_tool_keynote.py`:解析音频文件名,生成音源索引信息,生成合并后的音源文件
    - `main/load_sound_lib/src/esp_midi_flash_loader.c`:加载音源文件
    - `partitions.csv`:配置音源文件的烧录地址和大小

3. 如果获取更多音色库和音频文件?

    您可以访问以下网站获取更多音色库和音频文件:

    - [Polyphone](https://www.polyphone.io/en/soundfonts)
    - [Musical Artifacts](https://musical-artifacts.com/)

To create a project from this example, run:

idf.py create-project-from-example "espressif/esp_midi=1.0.0:keyboard"

or download archive (~11.71 MB)