Drive local song titles from CSV and add 海阔天空 glyphs.

UI shows LocalSongNames instead of bare indices; pack regenerates the header; WQY font gains the four display characters.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yuquanjun 2026-09-04 22:15:46 +08:00
parent 7b5667b179
commit 8c3c14ccf8
7 changed files with 213 additions and 5 deletions

View File

@ -26,7 +26,7 @@ GUI_SWITCH mGuiData[] = {
GUI_SWITCH ParamGuiData[] =
{
{ 0, 0, 30, SONG_MODE_PARAM}, // 0 节奏类型1.bin约 31 首)
{ 0, 0, 0, EXPRESS_MODE_PARAM}, // 1 本地曲目2.bin,先 1 首可扩
{ 0, 0, 0, EXPRESS_MODE_PARAM}, // 1 本地曲目2.binMax 由 UI_ReloadTonePreset←LOCAL_SONG_COUNT
{ 0, 0, 2, ALL_MODE_PARAM}, // 2
};

View File

@ -130,7 +130,16 @@ void UI_ReloadTonePreset(void)
UI_ApplyToneAddress();
if (mGuiData[GUI_AUTOBAND_SW].Current)
{
AutoBandTop1_LoadPresetItemFromFlash(ParamGuiData[EXPRESS_MODE_PARAM].Current);
GUI_SWITCH *local = &ParamGuiData[EXPRESS_MODE_PARAM];
local->Max = (LOCAL_SONG_COUNT > 0) ? (uint16_t)(LOCAL_SONG_COUNT - 1) : 0;
if (local->Current > local->Max)
local->Current = local->Max;
AutoBandTop1_LoadPresetItemFromFlash(local->Current);
{
int bin_n = AutoBandTop1_GetPresetItemCount();
if (bin_n != LOCAL_SONG_COUNT)
LOG_W("tone", "local song count csv=%d bin=%d", LOCAL_SONG_COUNT, bin_n);
}
return;
}
switch (mGuiData[GUI_TAB_INDEX].Current)
@ -377,7 +386,10 @@ void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group)
// 拼接序号 1. 2. 3.
sprintf(NumBuf, "%d.", idx);
uint16_t num_w = (uint16_t)(strlen(NumBuf) * UI0902_ASCII_W(UI->Text_size));
GET_ParamNum_Str(Group->Current, 0, Textstr_new, Group->Id);
if (Group->Id == EXPRESS_MODE_PARAM && Group->Current < LOCAL_SONG_COUNT)
strcpy(Textstr_new, LocalSongNameGbk[Group->Current]);
else
GET_ParamNum_Str(Group->Current, 0, Textstr_new, Group->Id);
uint32_t string_start_x = LCD_ShowMixedString(UI->x+10, UI->x + UI->w, text_y, (uint8_t *)Textstr_new , LCD_ALIGN_CENTER, WHITE, BLACK, UI->Text_size, 1);
//序号X = 中文起点 - 数字宽度 - 5<EFBC8C>?像素
uint16_t num_x = string_start_x - num_w;
@ -953,8 +965,11 @@ void GET_ParamNum_Str(int16_t NUM, uint8_t ValidLen, char* Str, uint8_t hasSign)
}
return;
break;
case 1:
sprintf(Str, "%d", NUM);
case 1: /* EXPRESS_MODE_PARAM本地曲目显示名CSV→LocalSongNames.h */
if (NUM >= 0 && NUM < LOCAL_SONG_COUNT)
strcpy(Str, LocalSongNameGbk[NUM]);
else
sprintf(Str, "%d", NUM);
return;
break;
case 2:

View File

@ -55,6 +55,11 @@ const char CHS8Table[][2] = {
{0xB3,0xA7}, // 厂
{0xC9,0xE8}, // 设
{0xD6,0xC3}, // 置
{0xBA,0xA3}, // 海
{0xC0,0xAB}, // 阔
{0xCC,0xEC}, // 天
{0xBF,0xD5}, // 空
};
const char CHS9Table[][2] = {
@ -116,6 +121,11 @@ const char CHS9Table[][2] = {
{0xD0,0xCD}, // 型
{0xB5,0xD8}, // 地
{0xC4,0xBF}, // 目
{0xBA,0xA3}, // 海
{0xC0,0xAB}, // 阔
{0xCC,0xEC}, // 天
{0xBF,0xD5}, // 空
};
const char CHS11Table[][2] = {
@ -177,6 +187,11 @@ const char CHS11Table[][2] = {
{0xD0,0xCD}, // 型
{0xB5,0xD8}, // 地
{0xC4,0xBF}, // 目
{0xBA,0xA3}, // 海
{0xC0,0xAB}, // 阔
{0xCC,0xEC}, // 天
{0xBF,0xD5}, // 空
};
@ -237,6 +252,11 @@ const char CHS12Table[][2] = {
{0xD0,0xCD}, // 型
{0xB5,0xD8}, // 地
{0xC4,0xBF}, // 目
{0xBA,0xA3}, // 海
{0xC0,0xAB}, // 阔
{0xCC,0xEC}, // 天
{0xBF,0xD5}, // 空
};
const char CHS13Table[][2] = {
@ -425,6 +445,11 @@ const char CHS13Table[][2] = {
{0xDC, 0xB0},// 馨
{0xC1,0xAC}, // 连
{0xCA,0xC7}, // 是
{0xBA,0xA3}, // 海
{0xC0,0xAB}, // 阔
{0xCC,0xEC}, // 天
{0xBF,0xD5}, // 空
};
@ -612,6 +637,11 @@ const char CHS15Table[][2] = {
{0xB6,0xD3}, // 队
{0xD1,0xF4}, // 阳
{0xDC,0xB0}, // 馨
{0xBA,0xA3}, // 海
{0xC0,0xAB}, // 阔
{0xCC,0xEC}, // 天
{0xBF,0xD5}, // 空
};
const char CHS16Table[][2] = {
@ -803,6 +833,10 @@ const char CHS16Table[][2] = {
{0xC6,0xD5}, // 普
{0xCD,0xA8}, // 通
{0xD2,0xB5}, // 业
{0xBA,0xA3}, // 海
{0xC0,0xAB}, // 阔
{0xCC,0xEC}, // 天
{0xBF,0xD5}, // 空
};
@ -892,6 +926,10 @@ const unsigned char Chinese_font_8[][8] = {
{0x00,0x60,0x40,0x40,0x40,0x40,0x00,0x80},/*"厂",48*/
{0x0E,0x02,0x03,0xC0,0x5A,0x40,0x64,0x31},/*"设",49*/
{0x7E,0x7E,0x18,0xFF,0x7E,0x42,0x7E,0xFF},/*"置",50*/
{0x08,0x00,0x2E,0x3F,0x57,0x96,0x1F,0x06},/*"海",51*/
{0x1F,0x41,0x89,0x89,0xA9,0xDD,0xD7,0xC1},/*"阔",52*/
{0x7E,0x00,0x00,0xFF,0x18,0x00,0x00,0x81},/*"天",53*/
{0x18,0x41,0x00,0x00,0x3C,0x00,0x00,0x7F},/*"空",54*/
};
const unsigned char Chinese_font_12[][24] = {
@ -951,6 +989,10 @@ const unsigned char Chinese_font_12[][24] = {
{0x7E,0x40,0x29,0x40,0x29,0x40,0xFF,0x40,0x29,0x40,0x28,0x40,0x4C,0xC0,0x04,0x00,0x3F,0x80,0x04,0x00,0xFF,0xE0,0x00,0x00},/*"型",53*/
{0x21,0x00,0x25,0x00,0x25,0x40,0xF5,0xC0,0x27,0x40,0x2D,0x40,0x25,0x40,0x25,0xC0,0x35,0x20,0xC4,0x20,0x03,0xE0,0x00,0x00},/*"地",54*/
{0x3F,0x80,0x20,0x80,0x20,0x80,0x3F,0x80,0x20,0x80,0x20,0x80,0x3F,0x80,0x20,0x80,0x20,0x80,0x3F,0x80,0x20,0x80,0x00,0x00},/*"目",55*/
{0x44,0x00,0x27,0xE0,0x88,0x00,0x57,0xC0,0x15,0x40,0x2F,0xE0,0x29,0x40,0xC9,0x40,0x4F,0xE0,0x40,0x40,0x41,0x80,0x00,0x00},/*"海",56*/
{0x4F,0xE0,0x20,0x20,0xA1,0xA0,0x96,0x20,0xC2,0x20,0xAF,0xA0,0x92,0x20,0xAF,0xA0,0xE8,0xA0,0xAF,0xA0,0xA0,0x60,0x00,0x00},/*"阔",57*/
{0x7F,0xC0,0x04,0x00,0x04,0x00,0x04,0x00,0xFF,0xE0,0x04,0x00,0x0A,0x00,0x0A,0x00,0x11,0x00,0x20,0x80,0xC0,0x60,0x00,0x00},/*"天",58*/
{0x04,0x00,0x7F,0xE0,0x40,0x20,0x89,0x00,0x10,0x80,0x60,0x40,0x1F,0x80,0x04,0x00,0x04,0x00,0x04,0x00,0x7F,0xE0,0x00,0x00},/*"空",59*/
};
const unsigned char Chinese_font_9[][18] = {
@ -1012,6 +1054,10 @@ const unsigned char Chinese_font_9[][18] = {
{0x7D,0x00,0x13,0x00,0xFF,0x00,0x77,0x00,0x11,0x00,0x09,0x00,0x08,0x00,0x08,0x00,0xFF,0x80},/*"型",55*/
{0x02,0x00,0x0A,0x00,0xEB,0x00,0x6F,0x00,0x1B,0x00,0x0B,0x00,0x2B,0x00,0xC8,0x80,0x07,0x80},/*"地",56*/
{0x3E,0x00,0x00,0x00,0x3E,0x00,0x22,0x00,0x22,0x00,0x3E,0x00,0x00,0x00,0x3E,0x00,0x22,0x00},/*"目",57*/
{0x48,0x00,0x0F,0x80,0x0F,0x00,0x2F,0x00,0x1F,0x80,0x13,0x00,0xDF,0x80,0x41,0x80,0x42,0x00},/*"海",58*/
{0x5F,0x80,0x00,0x80,0x84,0x80,0x84,0x80,0x9E,0x80,0xA4,0x80,0xDE,0x80,0xDE,0x80,0x81,0x80},/*"阔",59*/
{0x7F,0x00,0x08,0x00,0x08,0x00,0xFF,0x80,0x08,0x00,0x14,0x00,0x14,0x00,0x00,0x00,0xC1,0x80},/*"天",60*/
{0x08,0x00,0x7F,0x80,0x80,0x00,0x00,0x00,0x41,0x00,0x1E,0x00,0x08,0x00,0x08,0x00,0x7F,0x80},/*"空",61*/
};
const unsigned char Chinese_font_11[][22] = {
@ -1073,6 +1119,10 @@ const unsigned char Chinese_font_11[][22] = {
{0x7E,0x40,0x29,0x40,0x29,0x40,0xFF,0x40,0x29,0x40,0x28,0x40,0x4C,0xC0,0x04,0x00,0x3F,0x80,0x04,0x00,0xFF,0xE0},/*"型",55*/
{0x21,0x00,0x25,0x00,0x25,0x40,0xF5,0xC0,0x27,0x40,0x2D,0x40,0x25,0x40,0x25,0xC0,0x35,0x20,0xC4,0x20,0x03,0xE0},/*"地",56*/
{0x3F,0x80,0x20,0x80,0x20,0x80,0x3F,0x80,0x20,0x80,0x20,0x80,0x3F,0x80,0x20,0x80,0x20,0x80,0x3F,0x80,0x20,0x80},/*"目",57*/
{0x44,0x00,0x27,0xE0,0x88,0x00,0x57,0xC0,0x15,0x40,0x2F,0xE0,0x29,0x40,0xC9,0x40,0x4F,0xE0,0x40,0x40,0x41,0x80},/*"海",58*/
{0x4F,0xE0,0x20,0x20,0xA1,0xA0,0x96,0x20,0xC2,0x20,0xAF,0xA0,0x92,0x20,0xAF,0xA0,0xE8,0xA0,0xAF,0xA0,0xA0,0x60},/*"阔",59*/
{0x7F,0xC0,0x04,0x00,0x04,0x00,0x04,0x00,0xFF,0xE0,0x04,0x00,0x0A,0x00,0x0A,0x00,0x11,0x00,0x20,0x80,0xC0,0x60},/*"天",60*/
{0x04,0x00,0x7F,0xE0,0x40,0x20,0x89,0x00,0x10,0x80,0x60,0x40,0x1F,0x80,0x04,0x00,0x04,0x00,0x04,0x00,0x7F,0xE0},/*"空",61*/
};
@ -1266,6 +1316,10 @@ const unsigned char Chinese_font_13[][26] = {
{0xFD,0xC0,0x11,0x40,0x7F,0xE0,0x57,0x40,0x55,0xC0,0xBF,0x20,0x84,0x00,0xFF,0xE0,0x20,0x80,0x7F,0xC0,0x20,0x80,0x3F,0x80,0x00,0x00},/*"馨",182*/
{0x01,0x00,0x41,0x00,0x2F,0xF0,0x22,0x00,0x04,0x80,0xE7,0xE0,0x20,0x80,0x2F,0xF0,0x20,0x80,0x20,0x80,0x50,0x80,0x8F,0xF0,0x00,0x00},/*"连",183*/
{0x1F,0x80,0x10,0x80,0x1F,0x80,0x10,0x80,0x1F,0x80,0x00,0x00,0xFF,0xF0,0x02,0x00,0x13,0xC0,0x12,0x00,0x2A,0x00,0xC7,0xF0,0x00,0x00},/*"是",184*/
{0x44,0x00,0x27,0xF0,0x08,0x00,0x87,0xE0,0x45,0x20,0x24,0xA0,0x3F,0xF0,0x29,0x20,0xC8,0xA0,0x4F,0xF0,0x40,0x20,0x40,0xC0,0x00,0x00},/*"海",185*/
{0x4F,0xE0,0x20,0x20,0xA1,0xA0,0x96,0x20,0xC2,0x20,0xAF,0xA0,0x92,0x20,0xAF,0xA0,0xE8,0xA0,0xAF,0xA0,0xA8,0xA0,0x80,0x60,0x00,0x00},/*"阔",186*/
{0x7F,0xE0,0x04,0x00,0x04,0x00,0x04,0x00,0xFF,0xF0,0x04,0x00,0x0A,0x00,0x0A,0x00,0x11,0x00,0x10,0x80,0x20,0x40,0xC0,0x30,0x00,0x00},/*"天",187*/
{0x04,0x00,0xFF,0xE0,0x80,0x20,0x91,0x20,0x20,0x80,0xC0,0x40,0x3F,0x80,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0xFF,0xE0,0x00,0x00},/*"空",188*/
};
@ -1456,6 +1510,10 @@ const unsigned char Chinese_font_15[][30] = {
{0x00,0x80,0xF8,0x80,0x88,0x80,0x90,0x80,0x90,0x80,0xA0,0x80,0x90,0x80,0x89,0x40,0x89,0x40,0xCA,0x20,0xB2,0x20,0x84,0x10,0x88,0x08,0x90,0x04,0x00,0x00},/*"队",180*/
{0x00,0x00,0x7D,0xF8,0x45,0x08,0x49,0x08,0x49,0x08,0x51,0x08,0x49,0xF8,0x45,0x08,0x45,0x08,0x65,0x08,0x59,0x08,0x41,0xF8,0x41,0x08,0x41,0x08,0x00,0x00},/*"阳",181*/
{0x10,0xE0,0xFE,0xA0,0x11,0x18,0x7F,0xF0,0x52,0x90,0x7E,0x60,0x42,0x90,0xBF,0xF0,0x02,0x00,0xFF,0xF8,0x50,0x50,0x9F,0xC8,0x10,0x40,0x1F,0xC0,0x00,0x00},/*"馨",182*/
{0x42,0x00,0x22,0x00,0x23,0xFC,0x04,0x00,0x93,0xF0,0x52,0x90,0x22,0x50,0x2F,0xFC,0x24,0x90,0xC4,0x50,0x44,0x10,0x47,0xFC,0x40,0x10,0x40,0x60,0x00,0x00},/*"海",183*/
{0x20,0x08,0x1B,0xFC,0x08,0x08,0x50,0x68,0x4B,0x88,0x40,0x88,0x57,0xF8,0x48,0x88,0x4B,0xE8,0x5A,0x28,0x4A,0x28,0x4B,0xE8,0x4A,0x28,0x40,0x18,0x00,0x00},/*"阔",184*/
{0x00,0x00,0x3F,0xF0,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0xFF,0xFC,0x02,0x00,0x05,0x00,0x04,0x80,0x08,0x40,0x10,0x20,0x20,0x1C,0xC0,0x08,0x00,0x00},/*"天",185*/
{0x04,0x00,0x02,0x00,0x7F,0xF8,0x40,0x10,0x88,0x80,0x10,0x40,0x20,0x20,0x40,0x10,0x3F,0xE0,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0xFF,0xF8,0x00,0x00},/*"空",186*/
};
const unsigned char Chinese_font_16[][32] = {
@ -1645,6 +1703,10 @@ const unsigned char Chinese_font_16[][32] = {
{0x08,0x20,0x04,0x40,0x7F,0xFC,0x04,0x40,0x24,0x48,0x14,0x50,0xFF,0xFE,0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10},/*"普",183*/
{0x00,0x00,0x47,0xF8,0x20,0x10,0x21,0xA0,0x00,0x40,0x07,0xFC,0xE4,0x44,0x24,0x44,0x27,0xFC,0x24,0x44,0x24,0x44,0x27,0xFC,0x24,0x44,0x24,0x54,0x54,0x08,0x8F,0xFE},/*"通",184*/
{0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x40,0x44,0x44,0x24,0x44,0x24,0x48,0x14,0x48,0x14,0x50,0x14,0x60,0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x40,0xFF,0xFE,0x00,0x00},/*"业",185*/
{0x01,0x00,0x21,0x00,0x11,0xFC,0x12,0x00,0x85,0xF8,0x41,0x08,0x49,0x48,0x09,0x28,0x17,0xFE,0x11,0x08,0xE2,0x48,0x22,0x28,0x23,0xFC,0x20,0x08,0x20,0x50,0x00,0x20},/*"海",186*/
{0x20,0x00,0x17,0xFC,0x00,0x04,0x48,0x34,0x45,0xC4,0x64,0x44,0x50,0x44,0x51,0xF4,0x44,0x44,0x48,0x44,0x79,0xF4,0x49,0x14,0x49,0x14,0x49,0xF4,0x40,0x04,0x40,0x0C},/*"阔",187*/
{0x00,0x00,0x3F,0xF8,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x02,0x80,0x02,0x80,0x04,0x40,0x04,0x40,0x08,0x20,0x10,0x10,0x20,0x08,0xC0,0x06},/*"天",188*/
{0x02,0x00,0x01,0x00,0x7F,0xFE,0x40,0x02,0x88,0x24,0x10,0x10,0x20,0x08,0x00,0x00,0x1F,0xF0,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x7F,0xFC,0x00,0x00},/*"空",189*/
};
const unsigned char Chinese_font_24[][72] = {

View File

@ -0,0 +1,17 @@
#ifndef __LOCAL_SONG_NAMES_H
#define __LOCAL_SONG_NAMES_H
/* Auto-generated by tools/gen_local_song_names.py from local_songs.csv — do not hand-edit. */
/* Display names are GBK for LCD_ShowMixedString; codes match 2.bin preset ASCII (e.g. HKTK). */
#define LOCAL_SONG_COUNT 1
static const char * const LocalSongNameGbk[LOCAL_SONG_COUNT] = {
"\xBA\xA3\xC0\xAB\xCC\xEC\xBF\xD5", /* 0: 海阔天空 */
};
static const char * const LocalSongCode[LOCAL_SONG_COUNT] = {
"HKTK", /* 0 */
};
#endif /* __LOCAL_SONG_NAMES_H */

View File

@ -38,6 +38,7 @@
#include "Drv_ILI9341_Lcd_Image_UI0902.h"
#include "UI0902_Res_Addr.h"
#include "ExtFlash_Tone_Addr.h"
#include "LocalSongNames.h"
#include "Drv_ILI9341_Lcd_Dump.h"
#include "Drv_XPT2046_Touch.h"

View File

@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Generate project/inc/LocalSongNames.h from Doc/音色文件/0903/local_songs.csv.
CSV columns: index,code,name
index preset index in 2.bin (0-based)
code ASCII code in bin preset name (e.g. HKTK from 1.HKTK)
name display name (Unicode); written as GBK byte escapes for the LCD font
Run standalone or from pack_extflash_tone_0903.py after packing 2.bin.
"""
from __future__ import annotations
import csv
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REPO = ROOT.parent.parent
CSV_PATH = REPO / "Doc" / "音色文件" / "0903" / "local_songs.csv"
OUT_HDR = ROOT / "project" / "inc" / "LocalSongNames.h"
def gbk_c_string(text: str) -> str:
raw = text.encode("gbk")
return "".join(f"\\x{b:02X}" for b in raw)
def load_rows(path: Path) -> list[dict]:
if not path.is_file():
raise FileNotFoundError(f"missing {path}")
with path.open("r", encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
rows = list(reader)
if not rows:
raise SystemExit(f"{path}: no song rows")
out = []
for i, row in enumerate(rows):
try:
idx = int((row.get("index") or "").strip())
except ValueError as e:
raise SystemExit(f"{path}: bad index on row {i + 2}: {row!r}") from e
code = (row.get("code") or "").strip()
name = (row.get("name") or "").strip()
if not code or not name:
raise SystemExit(f"{path}: empty code/name on row {i + 2}")
out.append({"index": idx, "code": code, "name": name})
out.sort(key=lambda r: r["index"])
for expect, row in enumerate(out):
if row["index"] != expect:
raise SystemExit(
f"{path}: indices must be contiguous from 0; "
f"expected {expect}, got {row['index']}"
)
return out
def write_header(rows: list[dict], path: Path) -> None:
n = len(rows)
name_lines = []
code_lines = []
for r in rows:
esc = gbk_c_string(r["name"])
name_lines.append(f' "{esc}", /* {r["index"]}: {r["name"]} */')
code_lines.append(f' "{r["code"]}", /* {r["index"]} */')
body = f"""#ifndef __LOCAL_SONG_NAMES_H
#define __LOCAL_SONG_NAMES_H
/* Auto-generated by tools/gen_local_song_names.py from local_songs.csv do not hand-edit. */
/* Display names are GBK for LCD_ShowMixedString; codes match 2.bin preset ASCII (e.g. HKTK). */
#define LOCAL_SONG_COUNT {n}
static const char * const LocalSongNameGbk[LOCAL_SONG_COUNT] = {{
{chr(10).join(name_lines)}
}};
static const char * const LocalSongCode[LOCAL_SONG_COUNT] = {{
{chr(10).join(code_lines)}
}};
#endif /* __LOCAL_SONG_NAMES_H */
"""
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(body, encoding="utf-8", newline="\n")
def main() -> int:
rows = load_rows(CSV_PATH)
write_header(rows, OUT_HDR)
print(f"Wrote {OUT_HDR} ({len(rows)} song(s) from {CSV_PATH.name})")
for r in rows:
print(f" [{r['index']}] {r['code']} {r['name']}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -17,6 +17,8 @@ Outputs:
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1] # firmware project root
@ -135,6 +137,12 @@ def main() -> None:
" AutoBand -> FLASH_ADDR_AUTOBAND_LEGACY 0x9EB5F (unchanged; overlaps 2.bin region — do not enable until remapped)",
" Boot logo -> UI0902_BOOT_LOGO_ADDR (full-screen); packed logo.bin only pads 0x0..0xCB70",
" Charging -> EXTFLASH_CHARGING_ADDR",
"",
"Add a local song:",
" 1. Pack the new preset into 2.bin",
" 2. Append a row to local_songs.csv (index,code,name)",
" 3. python tools/pack_extflash_tone_0903.py (also regenerates LocalSongNames.h)",
" 4. Rebuild firmware and flash MCU + ExtFlash",
]
OUT_MAP.write_text("\n".join(map_lines) + "\n", encoding="utf-8", newline="\n")
@ -147,6 +155,12 @@ def main() -> None:
raise SystemExit("ERROR: pack overflows into UI0902_RES_BASE")
print(f"OK: {0x00100000 - cursor} bytes free before UI0902 @ 0x00100000")
# Keep LocalSongNames.h in sync with Doc/.../local_songs.csv when packing tones.
gen = ROOT / "tools" / "gen_local_song_names.py"
r = subprocess.run([sys.executable, str(gen)], check=False)
if r.returncode != 0:
raise SystemExit(f"gen_local_song_names.py failed ({r.returncode})")
if __name__ == "__main__":
main()