K1Guitar/Global/Global.c

2215 lines
73 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "includes.h"
rt_mutex_t TM1629_Mutex = RT_NULL;
rt_mutex_t TM1617_Mutex = RT_NULL;
rt_mq_t MainTask_msg = RT_NULL;
rt_mq_t UITask_msg = RT_NULL;
rt_mq_t BLTask_msg = RT_NULL;
rt_sem_t UART4_sem = RT_NULL;
// 定义定时器ID
rt_timer_t OutTime_Stop_timer = RT_NULL;
const Touch_AreaTypeDef *pCurrentTouch_Area = Touch_ModeSelect_Areas;
GUI_SWITCH mGuiData[] = {
{ 0, 0, 11, GUI_TRANSPOSE}, // 0
{ 78, 40, 260, GUI_SPEED}, // 1
{ 0, 0, 1, GUI_MODE_PARAM}, // 2 ?? <20>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD>࿪<EFBFBD><E0BFAA> 0=<3D><> 1=<3D><>
{ 0, 0, 4, GUI_TIMBRE_SELECT}, // 3
{ 0, 0, 3, GUI_PLAY_SECTION}, // 4
{ 0, 0, 3, GUI_TAB_INDEX}, // 5
{ 0, 0, 1, GUI_AUTOBAND_SW}, // 6 ?? <20>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD>࿪<EFBFBD><E0BFAA> 0=<3D><> 1=<3D><>
{ 1, 0, 1, GUI_BL_SW},
{ 0, 0, 3, GUI_TAB_LAST_INDEX},
};
GUI_SWITCH ParamGuiData[] =
{
{ 0, 0, 30, SONG_MODE_PARAM}, // 0 节奏类型(1.bin,约 31 首)
{ 0, 0, 0, EXPRESS_MODE_PARAM}, // 1 本地曲目(2.bin;Max 由 UI_ReloadTonePreset←LOCAL_SONG_COUNT)
{ 0, 0, 2, ALL_MODE_PARAM}, // 2
};
const LEBEL_UI UI_Label[] = {
/* 0819:移调/速度 标签11、主值16 */
{UI0902_MARGIN_X, UI0902_CARD_W, UI0902_CARD_Y, UI0902_CARD_H,
(const uint8_t*)"\xD2\xC6\xB5\xF7", LCD_ALIGN_CENTER, WHITE,BLACK,UI0902_FONT_LABEL,
LCD_ALIGN_CENTER,WHITE,BLACK,UI0902_FONT_HERO, GUI_TRANSPOSE},
{UI0902_MARGIN_X + UI0902_CARD_W + UI0902_CARD_GAP, UI0902_CARD_W, UI0902_CARD_Y, UI0902_CARD_H,
(const uint8_t*)"\xCB\xD9\xB6\xC8", LCD_ALIGN_CENTER, WHITE,BLACK,UI0902_FONT_LABEL,
LCD_ALIGN_CENTER,WHITE,BLACK,UI0902_FONT_HERO, GUI_SPEED},
/* 0819:TAB/行标题11、曲目正文13 */
{UI0902_MARGIN_X, UI0902_CONTENT_W, UI0902_MODE_Y, UI0902_MODE_H,
(const uint8_t*)"\xB8\xE8\xC7\xFA\xD1\xA1\xD4\xF1", LCD_ALIGN_LEFT,WHITE,BLACK,UI0902_FONT_LABEL,
LCD_ALIGN_CENTER,WHITE,BLACK,UI0902_FONT_BODY,GUI_MODE_PARAM},
{UI0902_MARGIN_X, UI0902_CONTENT_W, UI0902_TIMBRE_Y, UI0902_TIMBRE_H,
(const uint8_t*)"\xCF\xD2\xD2\xF4\xC9\xAB\xD1\xA1\xD4\xF1",LCD_ALIGN_LEFT,WHITE,BLACK,UI0902_FONT_LABEL,
LCD_ALIGN_CENTER,WHITE,BLACK,UI0902_FONT_BODY,GUI_TIMBRE_SELECT},
/* 0819:演奏段落 字号13 */
{UI0902_MARGIN_X, UI0902_CONTENT_W, UI0902_SECTION_Y, UI0902_SECTION_H,
(const uint8_t*)"\xD1\xDD\xD7\xE0\xB6\xCE\xC2\xE4",LCD_ALIGN_LEFT,WHITE,BLACK,UI0902_FONT_BODY,
LCD_ALIGN_CENTER,WHITE,BLACK,UI0902_FONT_BODY,GUI_PLAY_SECTION},
};
/* 0904 / 0902-06:左→右 伴奏|弦|麦|麦混响,轨宽31 顶≈58 */
Fader_t faders[4] = {
{ 27, 58, 31, 215, 7, 0, 0, "\xB0\xE9\xD7\xE0" }, /* 0: 伴奏 */
{ 78, 58, 31, 215, 7, 1, 0, "\xCF\xD2" }, /* 1: 弦 */
{ 130, 58, 31, 215, 7, 2, 0, "\xC2\xF3" }, /* 2: 麦 */
{ 182, 58, 31, 215, 7, 3, 0, "\xC2\xF3\xBB\xEC\xCF\xEC" } /* 3: 麦混响 */
};
const Touch_AreaTypeDef Touch_Areas[] =
{
/* 状态栏蓝牙:点击开关(热区略大于图标) */
{ UI0902_BT_HIT_X0, UI0902_BT_HIT_X1, UI0902_BT_HIT_Y0, UI0902_BT_HIT_Y1,
0, 0, GUI_BL_SW, NULL },
/* Xmin,Xmax,Ymin,Ymax, Flag, Value, ID, action — 与 UI_Label 网格同步 */
{ 12, 64, UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, -1, 0, GUI_TRANSPOSE, NULL },
{ 65, 116, UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, 1, 0, GUI_TRANSPOSE, NULL },
{ 125, 176, UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, -1, 0, GUI_SPEED, NULL },
{ 177, 228, UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, 1, 0, GUI_SPEED, NULL },
/* 节奏类型 / 本地曲目 TAB(0902:12-66 / 72-127) */
{ 12, 66, UI0902_MODE_Y, UI0902_MODE_Y + UI0902_TAB_H - 1, 0, 0, GUI_AUTOBAND_SW, NULL },
{ 72, 127, UI0902_MODE_Y, UI0902_MODE_Y + UI0902_TAB_H - 1, 0, 1, GUI_AUTOBAND_SW, NULL },
{ 13, 120, UI0902_MODE_Y + UI0902_TAB_H + 4, UI0902_MODE_Y + UI0902_MODE_H - 1, -1, 0, GUI_MODE_PARAM, NULL },
{ 121, 227, UI0902_MODE_Y + UI0902_TAB_H + 4, UI0902_MODE_Y + UI0902_MODE_H - 1, 1, 0, GUI_MODE_PARAM, NULL },
{ 13, 120, UI0902_TIMBRE_Y, UI0902_TIMBRE_Y + UI0902_TIMBRE_H - 1, -1, 0, GUI_TIMBRE_SELECT, NULL },
{ 121, 227, UI0902_TIMBRE_Y, UI0902_TIMBRE_Y + UI0902_TIMBRE_H - 1, 1, 0, GUI_TIMBRE_SELECT, NULL },
/* 演奏段落 1~4(0902-04:12/69/125/182,宽 47,步进 57) */
{ 12, 58, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 0, GUI_PLAY_SECTION, NULL },
{ 69, 115, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 1, GUI_PLAY_SECTION, NULL },
{ 126, 172, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 2, GUI_PLAY_SECTION, NULL },
{ 183, 229, UI0902_SECTION_Y, UI0902_SECTION_Y + UI0902_SECTION_BTN_H - 1, 0, 3, GUI_PLAY_SECTION, NULL },
/* 底栏四键:万能 | 普通 | 专业 | 设置(60×55,y265–319) */
{ UI0902_NAV_X0(0), UI0902_NAV_X1(0), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_UNIVERSAL, GUI_NAV_BAR, NULL },
{ UI0902_NAV_X0(1), UI0902_NAV_X1(1), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_NORMAL, GUI_NAV_BAR, NULL },
{ UI0902_NAV_X0(2), UI0902_NAV_X1(2), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_EXPERT, GUI_NAV_BAR, NULL },
{ UI0902_NAV_X0(3), UI0902_NAV_X1(3), UI0902_NAV_Y0, UI0902_NAV_Y1, 0, UI0902_NAV_SETTING, GUI_NAV_BAR, NULL },
};
/* 推子 0~10 共 11 档;档10 取 AW816 上限附近 */
int16_t MIC_VOL_MAP[11]= {-9000,-7000,-5000,-3000,-1000,1000,3000,5000,7000,9000,1200};
uint8_t TOUCH_AREA_NUM = (sizeof(Touch_Areas)/sizeof(Touch_AreaTypeDef));
const uint8_t Version[3] = {0,2,28}; /* major.minor.patch — 0.2.28: 发布;万能前奏+普通/专业指板记忆与切模式沿用 */
uint8_t Led = 8;
bool PressFlag = 0;
void Return_Light_buff(uint8_t key_id_1629);
uint8_t Band_IsPlaying(void);
void Band_AbsorbEatenStart(void);
void Band_MarkRealStop(void);
void Band_CancelStartFollowup(void);
void Band_NotifyPlayActivity(void);
static void Band_StartLocalRetry(uint8_t midi, uint8_t *notes, uint8_t n);
static void Band_StartPieceRetry(uint8_t *notes, uint8_t n);
static void Band_SyncStartRetry(void);
static void Band_StopBeforeColdStart(void);
/* 1=喂 AutoBand 1ms 节拍;停止键软停时清 0,避免库继续出音且不必 Stop */
uint8_t Band_TickGate = 1;
uint8_t AutoCloseTime = 15;
uint16_t mAutoPowerOffCount = 15 * 60;
bool AutoCloseFlag = true;
void MainTask_Sendmsg(uint16_t ID, uint16_t ID2, uint16_t HiByte, uint16_t LoByte)
{
DisplayTaskMessage_Type msg;
rt_err_t ret;
msg.MessageType = ID; /* 业务消息 ID(MSG_ID_xxx) */
msg.ID = ID2;
msg.HiByte = HiByte;
msg.LoByte = LoByte;
ret = rt_mq_send(MainTask_msg, &msg, sizeof(DisplayTaskMessage_Type));
if (ret != RT_EOK) {
LOG_E("MSG", "send fail id=%u ret=%d", (unsigned)ID, (int)ret);
} else if (ID == MSG_ID_TOUCH || ID == MSG_ID_TOUCH_LONG_REPEAT ||
ID == MSG_ID_POWER_ON || ID == MSG_ID_POWER_OFF ||
ID == MSG_ID_BP_KEY || ID == MSG_ID_TAP_SPEED ||
ID == MSG_ID_BP_TRANSPOSE) {
LOG_I("MSG", "id=%u hi=%u lo=%u", (unsigned)ID, (unsigned)HiByte, (unsigned)LoByte);
}
}
void UITask_Sendmsg(uint16_t ID, uint16_t ID2, uint16_t HiByte, uint16_t LoByte)
{
DisplayTaskMessage_Type msg;
msg.MessageType = ID; /* 业务消息 ID(MSG_ID_xxx) */
msg.ID = ID2;
msg.HiByte = HiByte;
msg.LoByte = LoByte;
rt_mq_send(UITask_msg, &msg, sizeof(DisplayTaskMessage_Type));
}
void BLTask_Sendmsg(uint16_t ID, uint16_t ID2, uint16_t HiByte, uint16_t LoByte)
{
DisplayTaskMessage_Type msg;
msg.MessageType = ID; /* 业务消息 ID(MSG_ID_xxx) */
msg.ID = ID2;
msg.HiByte = HiByte;
msg.LoByte = LoByte;
rt_mq_send(BLTask_msg, &msg, sizeof(DisplayTaskMessage_Type));
}
#define KEY_PRESS_LEVEL RESET
#define PWR_ON_HOLD_MS 3000 /* 关机态长按 3s → 开机 */
#define PWR_OFF_HOLD_MS 2000 /* 开机态长按 2s → 关机 */
#define PWR_OFF_GRACE_MS 1500 /* 请求优雅关机后,超时仍未切轨则紧急断电+复位 */
typedef enum
{
KEY_STATE_IDLE,
KEY_STATE_PRESS,
KEY_STATE_NONE,
}PwrKeyState;
static PwrKeyState pwr_key_state = KEY_STATE_IDLE;
static uint32_t pwr_press_tick = 0;
/* 当前开关机状态(UI_Idle.c 等 extern 引用) */
bool powon = false;
/* 电源轨是否已切断(优雅关机与紧急关机共用;ISR 依此判断是否还需强切) */
static volatile uint8_t s_pwr_rails_cut = 0;
/* TMR7:关机长按已触发请求后的紧急倒计时(ms) */
static volatile uint16_t s_pwr_off_grace_ms = 0;
/* 电量 <6.2V:提示界面停留后关机 */
#define LOWBAT_SHUTDOWN_DELAY_MS 10000u
static volatile uint8_t s_lowbat_active = 0;
static volatile uint32_t s_lowbat_deadline_ms = 0;
/* ==================== 开机/关机动作 ==================== */
/* 仅 GPIO/背光:可在 ISR / HardFault 中调用,禁止 RTOS/Flash/日志 */
void PowerOff_CutRails(void)
{
s_pwr_rails_cut = 1u;
powon = false;
LCD_BLK_Clr();
BSP_MainPowerEnable(0);
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
}
static void PowerOff_EmergencyReset(void)
{
PowerOff_CutRails();
/* 主循环已死时无法画充电页:复位后按 USB 状态进充电/待机 */
nvic_system_reset();
}
void System_PowerOn(void)
{
LOG_I("PWR", "power_on begin");
/* 软关机不复位 MCU:清掉上次会话演奏状态,默认当前调一级大和弦(C 调=do mi sol) */
StartFlag = 0;
InModeFlag = false; /* 停在模式选择,禁止拨片/侧面键起奏 */
Accomp_ApplyDefaultIChord();
app_tm1629_reset_key_state();
s_pwr_rails_cut = 0u;
s_pwr_off_grace_ms = 0u;
s_lowbat_active = 0u;
s_lowbat_deadline_ms = 0u;
/* 本次长按已用于开机:先消耗,再置 powon,防止 TMR7 ISR 把同一按压力即判为关机 */
Power_Key_ConsumePress();
/* 控制电源硬件;蓝牙按 NVM/系统设置开关恢复,勿强制常开 */
BSP_MainPowerEnable(1);
BSP_DreamCorePowerEnable(1);
BSP_HT7178PowerEnable(1);
/* 蓝牙按 NVM/系统设置开关恢复,勿强制常开 */
BSP_BlueToothPowerEnable((uint8_t)(mGuiData[GUI_BL_SW].Current ? 1 : 0));
powon = true; /* 更新开机标志 */
/* 侧灯须在 POWER_ON/Logo 前就绪,否则彩灯会晚于 Logo */
TM1629D_Key_LED_Init();
app_tm1617_init();
/* 先通知 UI 亮屏,避免外设初始化阻塞导致长时间黑屏 */
LOG_I("PWR", "send POWER_ON");
MainTask_Sendmsg(MSG_ID_POWER_ON, 0, 0, 0);
AD85020B_Init();
XPT2046_Init();
App_Auto_Init();
/* App 协议走 UART4:须在开机后即解析,不能等到触摸选模式才 StartTask */
StartTask();
LOG_I("PWR", "periph ready + BT UART recv started");
}
static void PowerOn(void)
{
System_PowerOn();
}
static void PowerOff(void)
{
uint8_t nvm_ret;
LOG_I("PWR", "power_off begin");
powon = false; /* 更新关机标志 */
InModeFlag = false; /* 软关机后勿残留进模式,避免再开机模式选择页拨片起奏 */
s_pwr_off_grace_ms = 0u; /* 主路径已接管,取消紧急倒计时 */
StopFullTask();
/* 关机前把当前调音台推子与蓝牙开关写入 NVM,保证再次开机保持 */
{
NvmParam_Type *nvm = drv_nvm_param_ptr();
nvm->param.BandVol = faders[0].val;
nvm->param.StringVol = faders[1].val;
nvm->param.MIcVol = faders[2].val;
nvm->param.MicRev = faders[3].val;
nvm->param.BluetoothSW = (uint8_t)(mGuiData[GUI_BL_SW].Current ? 1 : 0);
}
nvm_ret = drv_nvm_save_to_flash();
LOG_I("PWR", "nvm_save=%u", (unsigned)nvm_ret);
LCD_FillByColor(0, 0, 240, 320, BLACK);
LCD_BLK_Clr();
LCD_WR_REG(0x28); /* Display OFF */
app_tm1629_all_off();
app_tm1617_off();
/* BT 先留窗口转发已排队 ACK,再切全部电源轨 */
rt_thread_mdelay(30);
PowerOff_CutRails();
CurrUIProcress = IdleProcess;
MainTask_Sendmsg(MSG_ID_POWER_OFF, 0, 0, 0);
LOG_I("PWR", "power_off done (soft-off)");
/* Type-C 有供电:维持现有逻辑,显示 Charging Image(POWOFF_CHARG) */
{
uint8_t i;
for (i = 0; i < 4; i++)
{
app_adc_in1_scan_part(4);
if (usb_charging_state || app_adc_usb_is_plugged())
break;
rt_thread_mdelay(5);
}
if (usb_charging_state || app_adc_usb_is_plugged())
{
usb_charging_state = 1;
usb_charging_laststate = 1;
LOG_I("PWR", "soft-off with Type-C → charge UI");
MainTask_Sendmsg(MSG_ID_POWOFF_CHARG, percentage, 1, 0);
}
}
}
/* App remote power-off request (BLE cmd 05 00): executed in main-loop context,
so StopFullTask() never detaches the calling BT thread itself. */
static volatile uint8_t s_sys_poweroff_req = 0;
void System_RequestPowerOff(void)
{
s_sys_poweroff_req = 1;
/* 主循环若已卡死:TMR7 紧急倒计时仍会强切电源 */
if (s_pwr_off_grace_ms == 0u && !s_pwr_rails_cut)
s_pwr_off_grace_ms = PWR_OFF_GRACE_MS;
}
void System_PowerOff_Poll(void)
{
if (s_sys_poweroff_req)
{
s_sys_poweroff_req = 0;
if (powon)
PowerOff();
}
}
/* 电量 <6.2V:提示界面停留后关机 */
void System_LowBat_Arm(void)
{
if (s_lowbat_active)
return;
s_lowbat_active = 1;
s_lowbat_deadline_ms = rt_tick_get() * 1000u / RT_TICK_PER_SECOND + LOWBAT_SHUTDOWN_DELAY_MS;
LOG_I("PWR", "lowbat tip armed, power_off in %ums", (unsigned)LOWBAT_SHUTDOWN_DELAY_MS);
}
uint8_t System_LowBat_IsActive(void)
{
return s_lowbat_active;
}
void System_LowBat_Poll(void)
{
uint32_t now;
if (!s_lowbat_active || !powon)
return;
now = rt_tick_get() * 1000u / RT_TICK_PER_SECOND;
if ((int32_t)(now - s_lowbat_deadline_ms) >= 0)
{
s_lowbat_active = 0;
LOG_I("PWR", "lowbat tip timeout → power_off");
System_RequestPowerOff();
}
}
/*
* TMR7 1ms(优先级高于伴奏 TMR6):与主循环解耦的关机键扫描。
* 正常:请求优雅关机;超时未切轨 → 紧急断电并复位。
*/
static uint16_t s_isr_hold_ms = 0;
static uint8_t s_isr_fired = 0;
/* 开机时消耗本次按键:同一次长按已用于开机,须松开后重新长按 2s 才关机。
* 须在 powon 置 true 前调用,避免 ISR 在开机瞬间把本次长按误判为关机。 */
void Power_Key_ConsumePress(void)
{
s_isr_fired = 1;
}
void Power_Key_IsrTick(void)
{
flag_status st;
/* RTT/USB 烧 ExtFlash 期间禁止软关机,避免长按/粘键打断 FLASH_ACK */
if (app_log_flash_busy())
return;
st = BSP_GetPowerKey();
if (st == KEY_PRESS_LEVEL)
{
if (s_isr_hold_ms < 60000u)
s_isr_hold_ms++;
}
else
{
s_isr_hold_ms = 0;
s_isr_fired = 0;
}
/* 仅处理「开机态关机」;开机仍由主循环负责(需完整外设初始化) */
if (powon && !s_isr_fired && s_isr_hold_ms >= PWR_OFF_HOLD_MS)
{
s_isr_fired = 1;
System_RequestPowerOff();
}
if (s_pwr_off_grace_ms > 0u)
{
s_pwr_off_grace_ms--;
if (s_pwr_off_grace_ms == 0u && !s_pwr_rails_cut)
PowerOff_EmergencyReset();
}
}
/* HardFault / MemManage / BusFault / UsageFault:死循环中仍可长按关机 */
void Power_Key_FaultLoop(void)
{
uint32_t hold = 0;
for (;;)
{
if (BSP_GetPowerKey() == KEY_PRESS_LEVEL)
{
hold++;
if (hold >= PWR_OFF_HOLD_MS)
{
PowerOff_EmergencyReset();
}
}
else
{
hold = 0;
}
Delay_us(1000);
}
}
static rt_err_t Power_HardFault_Hook(void *context)
{
(void)context;
Power_Key_FaultLoop();
return -RT_ERROR; /* 不可达 */
}
void Power_Key_WatchdogInit(void)
{
rt_hw_exception_install(Power_HardFault_Hook);
wk_tmr7_pwrkey_init();
}
void Power_Key_Scan()
{
flag_status status = BSP_GetPowerKey();
uint32_t now = rt_tick_get();
uint32_t holdtick = 0;
/* 复位/烧录后:无 USB 则自动开机一次;有 Type-C 则进关机充电画面(勿进 Logo) */
if (powon == false && pwr_key_state == KEY_STATE_IDLE && pwr_press_tick == 0)
{
static uint8_t s_auto_poweron_pending = 1;
if (s_auto_poweron_pending)
{
uint8_t i;
s_auto_poweron_pending = 0;
/* 多采几次 USB 检测,避免上电瞬间误判 */
for (i = 0; i < 8; i++)
{
app_adc_in1_scan_part(4);
if (usb_charging_state || app_adc_usb_is_plugged())
break;
rt_thread_mdelay(5);
}
if (usb_charging_state || app_adc_usb_is_plugged())
{
usb_charging_state = 1;
usb_charging_laststate = 1;
LOG_I("PWR", "boot with USB → charge UI (no logo)");
CurrUIProcress = IdleProcess;
MainTask_Sendmsg(MSG_ID_POWOFF_CHARG, percentage, 1, 0);
pwr_key_state = KEY_STATE_NONE;
return;
}
LOG_I("PWR", "auto power_on");
PowerOn();
pwr_key_state = KEY_STATE_NONE; /* 忽略开机瞬间可能残留的按键电平 */
return;
}
}
switch(pwr_key_state)
{
case KEY_STATE_IDLE:
if(status == KEY_PRESS_LEVEL)
{
pwr_press_tick = now;
pwr_key_state = KEY_STATE_PRESS;
}
break;
case KEY_STATE_PRESS:
if(status != KEY_PRESS_LEVEL) /* 提前松开,放弃 */
{
pwr_press_tick = 0;
pwr_key_state = KEY_STATE_IDLE;
break;
}
holdtick = now - pwr_press_tick;
/* 关机长按 3s 开机;开机态关机改由 TMR7 ISR(主循环卡死仍可关) */
if(powon == false && holdtick >= PWR_ON_HOLD_MS)
{
LOG_I("PWR", "key power_on hold=%u", (unsigned)holdtick);
PowerOn();
pwr_key_state = KEY_STATE_NONE;
}
break;
case KEY_STATE_NONE:
if(status != KEY_PRESS_LEVEL) /* 松开后复位,允许下次触发 */
{
pwr_key_state = KEY_STATE_IDLE;
pwr_press_tick = 0;
}
break;
}
}
/* ============================================================
* 缺失符号补定义(AutoBandTop1 模块源码缺失,这些是它依赖的变量)
* ============================================================ */
/* UI 当前处理函数指针(UI_global.c extern 引用) */
void (*CurrUIProcress)(DisplayTaskMessage_Type xEvent);
/* UI 公共全局变量 */
volatile uint32_t ADDRESS = FLASH_ADDR_MODE_UNIVERSAL;
uint8_t KEY_ID_1629 = 0;
bool StartFlag = false;
bool InModeFlag = false;
uint8_t key_toggle[4] = {0};
/* ============================================================
* UI 公共函数补定义
* ============================================================ */
/* 自动关机:有操作时复位秒计数;主循环每秒递减,到 0 关机 */
void ResetAutoPowerCount(void)
{
if (!AutoCloseFlag || AutoCloseTime == 0)
return;
mAutoPowerOffCount = (uint16_t)(AutoCloseTime * 60);
}
void AutoPowerOff_Scan(void)
{
static uint32_t s_last_sec_tick = 0;
uint32_t now;
if (!powon || !AutoCloseFlag || AutoCloseTime == 0)
return;
now = rt_tick_get();
if (s_last_sec_tick == 0)
{
s_last_sec_tick = now;
return;
}
if ((now - s_last_sec_tick) < RT_TICK_PER_SECOND)
return;
s_last_sec_tick = now;
if (mAutoPowerOffCount > 0)
mAutoPowerOffCount--;
if (mAutoPowerOffCount == 0)
{
LOG_I("PWR", "auto power_off idle=%umin", (unsigned)AutoCloseTime);
PowerOff();
}
}
/* GUI 数值调整(简单实现:更新当前值) */
uint8_t GUI_Item_AjustValue(GUI_SWITCH * Group, int8_t Dir)
{
uint8_t ret= 0;
if (Dir > 0) {
if (Group->Current < Group->Max) {
Group->Current++;
ret = 1;
}
} else if (Dir < 0) {
if (Group->Current > Group->Min) {
Group->Current--;
ret = 1;
}
}
return ret;
}
void GUI_Item_AjustLoopValue(GUI_SWITCH * Group, int8_t Dir)
{
if (Dir > 0) {
if (Group->Current < Group->Max) {
Group->Current++;
} else {
Group->Current = Group->Min;
}
} else if (Dir < 0) {
if (Group->Current > Group->Min) {
Group->Current--;
} else {
Group->Current = Group->Max;
}
}
}
void Send_volume(uint8_t vol)
{
uint8_t mMasterSysExVolume[12] = { 0xF0, 0x41, 0x00, 0x42, 0x12, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00,0xF7};
memset(mMasterSysExVolume+8,vol,1);
SendMidiDataToDreamDSP(mMasterSysExVolume,12);
}
/* 重发当前主音量:开机补发 / 进模式主页 / 开始播放前调用。
* Dream 上电早期可能丢 SysEx,丢失后旋钮不动就不再重发,
* 会出现"图标低音量、实际音量大";在关键时机重发可自愈 */
void MasterVolume_Reapply(void)
{
uint8_t vol = app_adc_get_volume();
app_adc_sync_volume(vol); /* 兜底刷新 level,保证图标与下发值一致 */
Send_volume(vol);
}
/* 当前调一级大和弦:把位1 + maj + 自然音(再经 GUI_TRANSPOSE 移调 → C调即 do mi sol) */
void Accomp_ApplyDefaultIChord(void)
{
KEY_ID_1629 = 1;
chord_type_index_map[1].type = 0; /* maj */
chord_type_index_map[1].PitchOffset = 1; /* 自然,非 # */
PressFlag = 0;
}
/* 专业/普通共用:最后一次指板和弦(供普通无指板拨片)。
* 另记专业侧移调:0xFF=尚未记下;移调合法值 0~11。
* chord: 0=无记录;1~21=指板键。 */
static uint8_t s_saved_transpose = 0xFFu;
static uint8_t s_last_fret_chord = 0;
static void Accomp_RememberLastFretChord(uint8_t key)
{
if (key >= 1u && key <= 21u)
s_last_fret_chord = key;
}
static void Accomp_RememberTranspose(void)
{
s_saved_transpose = (uint8_t)mGuiData[GUI_TRANSPOSE].Current;
}
/* 停键或切模式前:按当前页记住和弦;专业再记移调 */
void Accomp_RememberPlayContext(void)
{
uint8_t tab = (uint8_t)mGuiData[GUI_TAB_INDEX].Current;
uint8_t last = (uint8_t)mGuiData[GUI_TAB_LAST_INDEX].Current;
if (tab == 1 || (tab == 3 && last == 1))
{
Accomp_RememberTranspose();
Accomp_RememberLastFretChord(KEY_ID_1629);
}
else if (tab == 2 || (tab == 3 && last == 2))
{
Accomp_RememberLastFretChord(KEY_ID_1629);
}
}
static void Expert_RememberStopContext(void)
{
Accomp_RememberPlayContext();
}
/* 普通无指板拨片:优先用记下的最后指板和弦(勿信切模式后的默认 KEY=一级);
* 移调用专业侧记下的值;都没有才退回一级。 */
static void FreeMode_ApplyLastFretChordForNoFretPick(void)
{
if (s_saved_transpose != 0xFFu &&
(uint8_t)mGuiData[GUI_TRANSPOSE].Current != s_saved_transpose)
{
mGuiData[GUI_TRANSPOSE].Current = s_saved_transpose;
UITask_Sendmsg(MSG_ID_BLE_UI_REFRESH, GUI_TRANSPOSE, 0, 0);
}
/* 切模式 NavBar 会把 KEY 打成一级,必须以记忆为准 */
if (s_last_fret_chord >= 1u && s_last_fret_chord <= 21u)
{
KEY_ID_1629 = s_last_fret_chord;
PressFlag = 0;
return;
}
if (KEY_ID_1629 >= 1u && KEY_ID_1629 <= 21u)
{
Accomp_RememberLastFretChord(KEY_ID_1629);
PressFlag = 0;
return;
}
Accomp_ApplyDefaultIChord();
}
void Accomp_UpdatePlayingChord(void)
{
uint8_t midi_note_buff[4] = {0,};
uint8_t note_cnt = 0;
uint8_t i;
if (!StartFlag)
return;
if (KEY_ID_1629 == 0 || KEY_ID_1629 == 22 || KEY_ID_1629 == 23)
Accomp_ApplyDefaultIChord();
note_cnt = GetChordNotesByType(KEY_ID_1629,
chord_type_index_map[KEY_ID_1629].type,
midi_note_buff);
for (i = 0; i < note_cnt; i++)
AutoBandTop1_Note_On(midi_note_buff[i], 0x50);
}
/* 尾奏期间冻结走向灯:保持全绿,避免 LOOP_LIGHT 按小节刷红 */
static uint8_t s_outro_led_all_green = 0;
/* 万能拨片:bit0=等前奏后进 piece,bit1=已见前奏舞台(省 RAM) */
static uint8_t s_univ_preamble_flags = 0;
#define UNIV_PRE_WAIT_PIECE 0x01u
#define UNIV_PRE_SAW_STAGE 0x02u
/* libAutoBand m_ABSystem:+0x59 舞台,+0x50 小节相位。
* 前奏挂起舞台 0x0E、尾奏挂起舞台 0x0B 都要等相位对上小节线才真正切入。
* 前奏中舞台 4、前奏结束后进 Run(5)。 */
extern uint8_t m_ABSystem[];
#define AB_STAGE_OFF 0x59u
#define AB_BAR_PHASE_OFF 0x50u
#define AB_STAGE_PRE_WAIT 0x0Eu
#define AB_STAGE_PRE 0x04u
#define AB_STAGE_POST_WAIT 0x0Bu
#define AB_STAGE_POST 0x0Cu
static void FreeMode_CancelEvenBeatStop(void);
static void FreeMode_ArmEvenBeatStop(void);
static void Accomp_FireSectionNotes(void)
{
uint8_t midi_note_buff[4] = {0,};
uint8_t note_cnt;
uint8_t i;
note_cnt = GetChordNotesByType(KEY_ID_1629,
chord_type_index_map[KEY_ID_1629].type,
midi_note_buff);
for (i = 0; i < note_cnt; i++)
AutoBandTop1_Note_On(midi_note_buff[i], 0x50);
}
/* 已在播:不要等下一小节。落到挂起舞台并把相位清零,下一毫秒 tick 即切入。 */
static void Accomp_PreambleCutNow(void)
{
m_ABSystem[AB_STAGE_OFF] = AB_STAGE_PRE_WAIT;
*(volatile uint32_t *)(m_ABSystem + AB_BAR_PHASE_OFF) = 0;
}
static void Accomp_PostambleCutNow(void)
{
if (m_ABSystem[AB_STAGE_OFF] != AB_STAGE_POST)
m_ABSystem[AB_STAGE_OFF] = AB_STAGE_POST_WAIT;
*(volatile uint32_t *)(m_ABSystem + AB_BAR_PHASE_OFF) = 0;
}
/* 万能拨片起奏:退出 piece → 前奏(不循环) → 待前奏结束后切入 piece 段落1 循环。 */
static void Univ_StartWithPreamble(uint8_t *notes, uint8_t n)
{
uint8_t session_was_on = StartFlag ? 1u : 0u;
(void)notes;
(void)n;
StartFlag = 1;
s_outro_led_all_green = 0;
mGuiData[GUI_PLAY_SECTION].Current = 0; /* 段落后续固定进段落1 */
if (session_was_on)
{
Band_MarkRealStop();
AutoBandTop1_Stop();
}
else
Band_StopBeforeColdStart();
/* 须先退出 piece,否则 Preamble 舞台切不过去(同尾奏) */
AutoBandTop1_SetPiecePlayMode(0, 0, 0);
AutoBandTop1_SetRunVar(0);
AutoBandTop1_SetLoopMode(0, 1); /* 循环主段落,不循环前奏 */
AutoBandTop1_Preamble(0);
MasterVolume_Reapply();
Band_SyncStartRetry();
wk_delay_ms(50);
Accomp_FireSectionNotes();
s_univ_preamble_flags = UNIV_PRE_WAIT_PIECE;
}
/* 前奏舞台结束后切入 piece 段落1 并保持 loop。 */
static void Univ_PollEnterPieceAfterPreamble(void)
{
uint8_t st;
uint8_t midi_note_buff[4] = {0,};
uint8_t note_cnt;
uint8_t chord_id;
if (!(s_univ_preamble_flags & UNIV_PRE_WAIT_PIECE) || !StartFlag)
return;
if (mGuiData[GUI_TAB_INDEX].Current != 0 &&
!(mGuiData[GUI_TAB_INDEX].Current == 3 &&
mGuiData[GUI_TAB_LAST_INDEX].Current == 0))
{
s_univ_preamble_flags = 0;
return;
}
st = m_ABSystem[AB_STAGE_OFF];
/* 前奏挂起/前奏中:记下已进入前奏,继续等播完 */
if (st == AB_STAGE_PRE_WAIT || st == AB_STAGE_PRE)
{
s_univ_preamble_flags |= UNIV_PRE_SAW_STAGE;
return;
}
/* 尚未见到前奏舞台:若已到 Run(5) 说明空前奏被跳过,可进 piece;
* 其它启动中舞台继续等,避免冷启动抢先切入 piece 砍掉前奏。 */
if (!(s_univ_preamble_flags & UNIV_PRE_SAW_STAGE) && st != 5u)
return;
s_univ_preamble_flags = 0;
AutoBandTop1_SetRunVar(0);
AutoBandTop1_SetPiecePlayMode(1, 0, 480);
AutoBandTop1_SetLoopMode(0, 1);
chord_id = KEY_ID_1629;
if (chord_id == 0 || chord_id == 22 || chord_id == 23)
{
Accomp_ApplyDefaultIChord();
chord_id = KEY_ID_1629;
}
note_cnt = GetChordNotesByType(chord_id, chord_type_index_map[chord_id].type,
midi_note_buff);
Band_StartPieceRetry(midi_note_buff, note_cnt);
Band_NotifyPlayActivity();
LOG_I("pick", "universal after-preamble piece name=%s notes=%u chord=%u",
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)note_cnt, (unsigned)chord_id);
}
/* 尾奏起奏:Postamble(0) + 同步起奏 + 当前和弦 Note_On。
* 供 ADC 独立尾奏键与万能模式第4段落键共用。
* 万能走 piece 循环,须先退出 piece,否则 Postamble 舞台切不过去;
* 尾奏 MIDI 须由音师写入 3.bin(见 Doc/音师需求_万能3.bin尾奏_20260914.md)。
* 已在播时清小节相位,避免库把尾奏挂到下一小节线。 */
void AutoBand_StartOutro(void)
{
uint8_t was_playing = StartFlag;
s_univ_preamble_flags = 0;
if (KEY_ID_1629 == 0 || KEY_ID_1629 == 22 || KEY_ID_1629 == 23)
Accomp_ApplyDefaultIChord();
/* 退出和弦走向 piece,再进风格尾奏舞台 */
AutoBandTop1_SetPiecePlayMode(0, 0, 0);
AutoBandTop1_Postamble(0);
/* 尾奏:指板 LED 全绿(停走 LOOP 走向红灯) */
s_outro_led_all_green = 1;
app_tm1629_all_on(LED_COLOR_G);
/* 尾奏起奏前补发主音量,保证与图标一致 */
MasterVolume_Reapply();
if (was_playing)
Accomp_PostambleCutNow();
else
{
Band_SyncStartRetry();
wk_delay_ms(50);
}
Accomp_FireSectionNotes();
}
void ADC_IN1_KEY_Handle(uint8_t key,bool on)
{
uint8_t is_preamble = 0;
uint8_t is_postamble = 0;
uint8_t was_playing;
(void)on;
/* 模式选择页:侧面键不得起奏 */
if (!InModeFlag)
return;
/* 万能:前奏/间奏/尾奏/断奏无响应(含 App 06 指令) */
if (mGuiData[GUI_TAB_INDEX].Current == 0)
return;
key_toggle[key] = !key_toggle[key];
if(key_toggle[key] == 1)
{
was_playing = StartFlag;
FreeMode_CancelEvenBeatStop();
//ResetAutoPowerCount();
if(key == 1)
{
/* 尾奏:与万能模式第4键共用同一起奏路径 */
AutoBand_StartOutro();
}
else
{
s_outro_led_all_green = 0; /* 前奏/断奏:恢复走向灯 */
switch(key)
{
case 3: AutoBandTop1_Preamble(0); is_preamble = 1; break;
case 0: AutoBandTop1_Preamble(1); is_preamble = 1; break;
case 2:
/* 断奏同尾奏:piece 循环里 Postamble 切不过去 */
AutoBandTop1_SetPiecePlayMode(0, 0, 0);
AutoBandTop1_Postamble(1);
is_postamble = 1;
break;
}
/* 前奏:须在 SyncStart 前落到当前调一级,否则前 50ms 伴奏按旧把位进 */
if (is_preamble)
Accomp_ApplyDefaultIChord();
else if (KEY_ID_1629 == 0 || KEY_ID_1629 == 22 || KEY_ID_1629 == 23)
Accomp_ApplyDefaultIChord();
/* 前奏/断奏起奏前补发主音量,保证与图标一致 */
MasterVolume_Reapply();
if (was_playing)
{
if (is_preamble)
Accomp_PreambleCutNow();
else if (is_postamble)
Accomp_PostambleCutNow();
}
else
{
Band_SyncStartRetry();
wk_delay_ms(50);
}
Accomp_FireSectionNotes();
if (is_preamble)
StartFlag = 1;
}
}
else
{
Stop_AutoBand();
//StartFlag = 0;
//tick_ever_positive = false;
for(uint8_t i = 0;i < 4;i++) key_toggle[i] = 0;
}
}
void TM1617_Handle(uint8_t key)
{
/* 调音台:段落数字键不得注入假触摸(会打在推子底部,把数值打到 1) */
if (CurrUIProcress == UI_Mixer_Process)
return;
if(mGuiData[GUI_TAB_INDEX].Current == 3)
{
uint8_t value = 0;
switch(key)
{
case 0: value = 0; break;
case 1: value = 1; break;
case 2: value = 2; break;
case 3: value = 3; break;
}
mGuiData[GUI_PLAY_SECTION].Current = value;
//AutoBandTop1_SetRunVar(mGuiData[GUI_PLAY_SECTION].Current);
//Refresh_SectionSelect(last_value, &UI_Label[GUI_PLAY_SECTION], &mGuiData[GUI_PLAY_SECTION]);
//Draw_Tab_Section_Menu(&UI_Label[GUI_PLAY_SECTION], mGuiData[GUI_PLAY_SECTION].Current);
}else
{
switch(key)
{
case 0: MainTask_Sendmsg(MSG_ID_TOUCH, 1, 40, 255); break;
case 1: MainTask_Sendmsg(MSG_ID_TOUCH, 1, 90, 255); break;
case 2: MainTask_Sendmsg(MSG_ID_TOUCH, 1, 140, 255); break;
case 3: MainTask_Sendmsg(MSG_ID_TOUCH, 1, 200, 255); break;
}
}
}
void Tap_to_Speed()
{
#define TAP_MIN_BPM 40 // 鏈€灏忛€熷害
#define TAP_MAX_BPM 260 // 鏈€澶ч€熷害
#define TAP_TIMEOUT_MS 1000 // 2绉掓病鏁插氨澶嶄綅
static int32_t tap_last_time = 0; // 涓婃鎸夐敭鏃堕棿
static uint8_t tap_state = 0; // 0=绌洪棽 1=宸叉暡涓€娆?
static uint16_t ui_tap_tempo = 120; // 鏈€缁堥€熷害 40~260
uint32_t now = rt_tick_get();
if (now - tap_last_time > TAP_TIMEOUT_MS) {
tap_state = 0;
}
if (tap_state == 0) {
tap_last_time = now;
tap_state = 1;
uint8_t AllNoteOff_buff[3] = {0x99,0x3C,0x50};
SendMidiDataToDreamDSP(AllNoteOff_buff,sizeof(AllNoteOff_buff));
}
else {
uint32_t delta = now - tap_last_time;
if (delta >= 200 && delta <= 1500)
{
ui_tap_tempo = 60000 / delta;
if (ui_tap_tempo < TAP_MIN_BPM) ui_tap_tempo = TAP_MIN_BPM;
if (ui_tap_tempo > TAP_MAX_BPM) ui_tap_tempo = TAP_MAX_BPM;
MainTask_Sendmsg(MSG_ID_TAP_SPEED, ui_tap_tempo, 0, 0);
uint8_t AllNoteOff_buff[3] = {0x99,0x3C,0x50};
SendMidiDataToDreamDSP(AllNoteOff_buff,sizeof(AllNoteOff_buff));
}
tap_state = 0;
}
}
/* 普通模式松手:对齐到下一偶拍末再停(拍1→拍2末,拍3→拍4末)。
* piece 窗口 1920=4 拍,每拍 480 tick(与起奏 SetPiecePlayMode(1,0,1920) 一致)。 */
#define FREE_BEAT_TICKS 480u
#define FREE_PIECE_TICKS 1920u
static uint32_t s_free_even_stop_tick = 0;
static uint8_t s_free_even_stop_beat = 2; /* 目标偶拍 2 或 4 */
static uint8_t s_free_even_flags = 0; /* bit0=armed bit1=seen target */
#define FREE_EVEN_ARM 0x01u
#define FREE_EVEN_SEEN 0x02u
static void FreeMode_CancelEvenBeatStop(void)
{
s_free_even_flags = 0;
s_free_even_stop_tick = 0;
s_free_even_stop_beat = 2;
}
/* 再次按住指板:取消待停并恢复 piece 循环 */
static void FreeMode_ResumePieceLoop(void)
{
if (!(s_free_even_flags & FREE_EVEN_ARM))
return;
FreeMode_CancelEvenBeatStop();
if (StartFlag && mGuiData[GUI_TAB_INDEX].Current == 2 &&
!mGuiData[GUI_AUTOBAND_SW].Current)
{
AutoBandTop1_SetPiecePlayMode(1, 0, FREE_PIECE_TICKS);
AutoBandTop1_SetLoopMode(0, 1);
}
}
static void FreeMode_ArmEvenBeatStop(void)
{
uint64_t play;
int beat;
int target;
if (!StartFlag)
{
FreeMode_CancelEvenBeatStop();
return;
}
play = AutoBandTop1_GetCurrentPlayTick();
beat = AutoBandTop1_GetCurrentBeat();
if (beat < 1)
beat = 1;
/* 拍1→2,拍2→2,拍3→4,拍4→4 */
target = ((beat + 1) / 2) * 2;
s_free_even_stop_beat = (uint8_t)target;
s_free_even_flags = FREE_EVEN_ARM |
((beat >= target) ? FREE_EVEN_SEEN : 0u);
/* ((beat0/2)+1)*2*480 → 拍1/2→960,拍3/4→1920,跨小节同理;piece API 仅 32bit */
{
uint64_t stop =
((play / FREE_BEAT_TICKS) / 2ull + 1ull) * 2ull * (uint64_t)FREE_BEAT_TICKS;
s_free_even_stop_tick = (stop > 0xFFFFFFFFull) ? 0xFFFFFFFFu : (uint32_t)stop;
}
s_free_even_flags |= FREE_EVEN_ARM;
/* 禁循环并裁 piece,库播到 stop tick 自停;Poll 作兜底 */
AutoBandTop1_SetLoopMode(0, 0);
AutoBandTop1_SetPiecePlayMode(1, 0, s_free_even_stop_tick);
LOG_I("pick", "free even-stop arm beat=%d target=%d play=%u stop=%u",
beat, target, (unsigned)play, (unsigned)s_free_even_stop_tick);
}
void FreeMode_PollEvenBeatStop(void)
{
int beat;
uint8_t tick_done;
uint8_t beat_done;
if (!(s_free_even_flags & FREE_EVEN_ARM))
return;
if (PressFlag || mGuiData[GUI_TAB_INDEX].Current != 2 || !StartFlag)
{
FreeMode_CancelEvenBeatStop();
return;
}
beat = AutoBandTop1_GetCurrentBeat();
if (beat == (int)s_free_even_stop_beat)
s_free_even_flags |= FREE_EVEN_SEEN;
/* 已到达目标偶拍后离开该拍 = 该拍播完;或 play tick 到点 */
beat_done = ((s_free_even_flags & FREE_EVEN_SEEN) &&
beat != (int)s_free_even_stop_beat && beat != 0) ? 1u : 0u;
tick_done = (AutoBandTop1_GetCurrentPlayTick() >= s_free_even_stop_tick) ? 1u : 0u;
if (tick_done || beat_done)
{
FreeMode_CancelEvenBeatStop();
Stop_AutoBand_Request();
LOG_I("pick", "free even-stop done beat=%d tick=%u beatDone=%u",
beat, (unsigned)tick_done, (unsigned)beat_done);
}
}
static void Band_MarkCleanStopped(void);
static void Band_StopBeforeColdStart(void);
/* 扫描线程只投递:真正 Stop 在 MainTask,与拨片 Pick 同队列串行。
* 若在扫描线程同步 Stop,会与 MainTask 拨片并发:Stop 期间 StartFlag 仍为 1,
* 拨片误判 Band_IsPlaying → 只改和弦不起奏,需再等 ~0.5s 才能拨响。 */
void Stop_AutoBand_Request(void)
{
/* ID=1:停止键软停(静音+停节拍,不调库 Stop,否则随后 Start 被吞) */
MainTask_Sendmsg(MSG_ID_STOP_BAND, 1, 0, 0);
}
static void Band_AllNotesOff(void)
{
uint8_t i;
for (i = 0; i < 16; i++)
{
uint8_t AllNoteOff_buff[3] = {(uint8_t)(0xB0 | i), 0x7B, 0x00};
SendMidiDataToDreamDSP(AllNoteOff_buff, sizeof(AllNoteOff_buff));
uint8_t AllControlOff_buff[3] = {(uint8_t)(0xB0 | i), 0x79, 0x00};
SendMidiDataToDreamDSP(AllControlOff_buff, sizeof(AllControlOff_buff));
}
}
/* 停止键/偶拍收束:立刻静音并暂停库 1ms 节拍,不调用 AutoBandTop1_Stop。
* 库 Stop 之后第一个 Start 必被吞,立即拨片无声。 */
void Stop_AutoBand_Soft(void)
{
Expert_RememberStopContext();
FreeMode_CancelEvenBeatStop();
Band_CancelStartFollowup();
Band_TickGate = 0;
StartFlag = 0;
KEY_ID_1629 = 0;
Band_MarkCleanStopped();
Band_AllNotesOff();
s_outro_led_all_green = 0;
s_univ_preamble_flags = 0;
for (uint8_t i = 0; i < 4; i++) key_toggle[i] = 0;
}
void Stop_AutoBand()
{
Expert_RememberStopContext();
FreeMode_CancelEvenBeatStop();
/* 先清播放态,再 Stop/AllNoteOff,避免处理期间其它路径误判在播 */
Band_CancelStartFollowup();
Band_TickGate = 1;
StartFlag = 0;
KEY_ID_1629 = 0;
Band_MarkCleanStopped();
AutoBandTop1_Stop();
Band_AllNotesOff();
s_outro_led_all_green = 0;
s_univ_preamble_flags = 0;
//tick_ever_positive = false;
for(uint8_t i = 0;i < 4;i++) key_toggle[i] = 0;
}
uint8_t BP_Transpose(uint8_t key_idx)
{
if(pick_hold_up)
{
/* 快速移调模式:按键只用于选择移调值,不切换和弦指型 */
uint8_t new_trans = 15;
switch(key_idx)
{
case 2: new_trans = 0; break;
case 3: new_trans = 1; break;
case 5: new_trans = 2; break;
case 6: new_trans = 3; break;
case 8: new_trans = 4; break;
case 9: new_trans = 5; break;
case 11: new_trans = 5; break;
case 12: new_trans = 6; break;
case 14: new_trans = 7; break;
case 15: new_trans = 8; break;
case 17: new_trans = 9; break;
case 18: new_trans = 10; break;
case 20: new_trans = 11; break;
}
if(new_trans != 15)
{
LOG_I("xpose", "bp key=%u trans=%u", (unsigned)key_idx, (unsigned)new_trans);
MainTask_Sendmsg(MSG_ID_BP_TRANSPOSE, new_trans, 0, 0);
return 1;
}
}
return 0;
}
/* 伴奏活动跟踪:AutoBand PlayTick 前进即视为播放中。
* 供专业模式指板键区分「播放中换和弦」与「段落结束后续奏重启」。 */
static uint64_t s_play_tick_last = 0;
static uint32_t s_play_adv_tick = 0; /* 最近一次 PlayTick 前进的系统 tick */
/* 1=下次冷启动前需先 Stop 清库残留;0=Stop_AutoBand 已停干净,勿再 Stop。 */
static uint8_t s_band_need_stop_before_start = 1;
/* 库特性(RTT 实测):真实停播后第一个完整 Start 被吞
* (StartFlag=1 但 PlayTick 归 0 且不前进);第二次完整 Start 才起奏。
* 哑 Start 不能用来吸收,会占住会话把真正起奏也吃掉。 */
static uint8_t s_band_eater_armed = 0;
static void Band_MarkCleanStopped(void)
{
s_play_tick_last = 0;
s_play_adv_tick = 0;
s_band_need_stop_before_start = 0;
}
/* 一切可能真实停播的 Stop 路径调用:武装「停后首启被吞」标记。
* 宁可误武装(哑 Start 未被吞也只会被紧随的真实 Start 立刻重触发,无感),
* 不可漏武装(漏了用户就要拨两次)。 */
void Band_MarkRealStop(void)
{
s_band_eater_armed = 1;
}
/* 停后首个完整 Start 会被库吞掉(PlayTick 归 0 且不前进)。
* 同一次调用里连发两次会被合成一次,仍然被吞;必须隔一拍再 Start。
* 停后再起:先发本次 Start(可能被吞),再 20ms 后由 MainTask 补发一次。 */
static uint8_t s_start_followup_pending;
static uint8_t s_start_followup_force; /* 1=补发中,禁止走「在播只换和弦」 */
static rt_timer_t s_start_followup_timer;
static uint8_t Band_TakeEaterArmed(void)
{
uint8_t armed = s_band_eater_armed;
s_band_eater_armed = 0;
return armed;
}
static void Band_StartFollowupTimerCb(void *parameter)
{
(void)parameter;
MainTask_Sendmsg(MSG_ID_START_FOLLOWUP, 0, 0, 0);
}
void Band_CancelStartFollowup(void)
{
s_start_followup_pending = 0;
s_start_followup_force = 0;
if (s_start_followup_timer != RT_NULL)
rt_timer_stop(s_start_followup_timer);
}
uint8_t Band_TakeStartFollowup(void)
{
uint8_t pending = s_start_followup_pending;
s_start_followup_pending = 0;
return pending;
}
void Band_BeginStartFollowup(void)
{
s_start_followup_force = 1;
}
void Band_EndStartFollowup(void)
{
s_start_followup_force = 0;
}
static void Band_PostStartFollowup(void)
{
if (s_start_followup_pending)
return;
s_start_followup_pending = 1;
if (s_start_followup_timer == RT_NULL)
{
s_start_followup_timer = rt_timer_create("bandFu",
Band_StartFollowupTimerCb,
RT_NULL,
500, /* 库吞掉停后首启;约 0.5s 后再 Start 才生效 */
RT_TIMER_FLAG_ONE_SHOT);
}
if (s_start_followup_timer != RT_NULL)
{
rt_timer_stop(s_start_followup_timer);
rt_timer_start(s_start_followup_timer);
}
else
MainTask_Sendmsg(MSG_ID_START_FOLLOWUP, 0, 0, 0);
}
static void Band_StartLocalRetry(uint8_t midi, uint8_t *notes, uint8_t n)
{
Band_TickGate = 1;
AutoBandTop1_StartWithNote(midi);
AutoBandTop1_StartWithNotes(notes, n);
}
static void Band_StartPieceRetry(uint8_t *notes, uint8_t n)
{
Band_TickGate = 1;
AutoBandTop1_StartWithNotes(notes, n);
}
static void Band_SyncStartRetry(void)
{
Band_TickGate = 1;
AutoBandTop1_SyncStart();
}
void Band_AbsorbEatenStart(void)
{
(void)0;
}
/* 冷启动前按需 Stop:已由停止键停干净则跳过 */
static void Band_StopBeforeColdStart(void)
{
if (s_band_need_stop_before_start)
{
Band_MarkRealStop(); /* 若库仍在播,此 Stop 会武装吞噬器,由紧随的吸收处理 */
AutoBandTop1_Stop();
}
s_band_need_stop_before_start = 1;
}
/* 诊断钩子:强制下次冷启动前先 Stop(RTT `band dirty`) */
void Band_ForceStopBeforeStart(void)
{
s_band_need_stop_before_start = 1;
}
void Band_TrackPlayTick(void)
{
uint64_t pt;
if (!StartFlag)
{
s_play_tick_last = 0;
s_play_adv_tick = 0;
return;
}
pt = AutoBandTop1_GetCurrentPlayTick();
if (pt != s_play_tick_last)
{
s_play_tick_last = pt;
s_play_adv_tick = rt_tick_get();
}
}
/* 段落键 SetRunVar 后 PlayTick 可能短暂不变;主动刷新以免误判为已停。 */
void Band_NotifyPlayActivity(void)
{
if (!StartFlag)
return;
s_play_tick_last = AutoBandTop1_GetCurrentPlayTick();
s_play_adv_tick = rt_tick_get();
}
uint8_t Band_IsPlaying(void)
{
if (!StartFlag || s_play_adv_tick == 0)
return 0;
return (rt_tick_get() - s_play_adv_tick) < (RT_TICK_PER_SECOND / 4);
}
/* 专业模式公共起奏:拨片首次起奏与起奏后的指板续奏共用。
* 节奏库走 piece 循环;本地曲目按当前段落走 StartWithNote。 */
static void Expert_StartWithChord(uint8_t chord_id, uint8_t *midi_note_buff,
uint8_t note_cnt, const char *trigger)
{
uint8_t midi = 0;
uint8_t session = StartFlag ? 1u : 0u;
StartFlag = 1;
if (mGuiData[GUI_AUTOBAND_SW].Current)
{
/* 会话内勿 Stop:段落圆键切变奏后指板续奏若 Stop 会直接关音;
* 与节奏类型一致,仅换和弦/重进当前段。
* 冷启动:仅当库可能仍有残留时才 Stop;停止键后已停干净则跳过。 */
if (!session)
Band_StopBeforeColdStart();
AutoBandTop1_SetLoopMode(0, 1);
AutoBandTop1_SetRunVar(mGuiData[GUI_PLAY_SECTION].Current);
midi = 0x3C + mGuiData[GUI_TRANSPOSE].Current;
Band_StartLocalRetry(midi, midi_note_buff, note_cnt);
Band_NotifyPlayActivity();
LOG_I("pick", "local/expert root=%u name=%s sw=%u tab=%u trigger=%s chord=%u",
midi,
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)mGuiData[GUI_AUTOBAND_SW].Current,
(unsigned)mGuiData[GUI_TAB_INDEX].Current,
trigger,
(unsigned)chord_id);
}
else
{
AutoBandTop1_SetPiecePlayMode(1, 0, 1920);
Band_StartPieceRetry(midi_note_buff, note_cnt);
AutoBandTop1_SetLoopMode(0, 1);
Band_NotifyPlayActivity();
LOG_I("pick", "expert/rhythm piece name=%s trigger=%s chord=%u",
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
trigger,
(unsigned)chord_id);
}
}
void TM1629_Handle(uint8_t key)
{
STRING_MIDI *MIDI;
const STRING_MIDI *ORGAN_MIDI;
/* 万能:指板不改和弦、不启停伴奏;保留拍速/停止/拨片上推快移调 */
if (mGuiData[GUI_TAB_INDEX].Current == 0)
{
if (key == 22) { Tap_to_Speed(); return; }
if (key == 23) { Stop_AutoBand_Request(); return; }
if (BP_Transpose(key)) return;
LOG_I("xpose", "uni skip key=%u hold=%u", (unsigned)key, (unsigned)pick_hold_up);
return; /* 忽略 0~21:不改 KEY_ID/USE_MIDI/PressFlag,不启停 OutTime_Stop */
}
if(BP_Transpose(key)) return;
switch(key)
{
case 0:
PressFlag = 0;
if(mGuiData[GUI_TAB_INDEX].Current == 2)
{
/* 普通:松手对齐偶拍末再停。
* 收束前勿清 KEY_ID:App_Auto 音区指纹依赖指板键,清零会关掉 III+ 的 -12,
* 表现为停前音高突然升高。 */
if (StartFlag)
FreeMode_ArmEvenBeatStop();
else
KEY_ID_1629 = 0;
}
else if(mGuiData[GUI_TAB_INDEX].Current == 0)
{
//StartFlag = 0;
//tick_ever_positive = false;
//osTimerStart(Delay_stop_timer,1000);
rt_timer_start(OutTime_Stop_timer);
}
break;
case 22:Tap_to_Speed(); break;
case 23:Stop_AutoBand_Request(); break;
default: // 1 - 21
if (key >= 1 && key <= 21)
{
if(mGuiData[GUI_TAB_INDEX].Current == 0)
{
PressFlag = 1;
rt_timer_stop(OutTime_Stop_timer);
//osTimerStop(Delay_stop_timer);
}
else if(mGuiData[GUI_TAB_INDEX].Current == 2)
{
/* 普通:按住指板;若正在偶拍收束则恢复循环续奏 */
FreeMode_ResumePieceLoop();
PressFlag = 1;
}
uint8_t group = (key - 1) / 3; /* 1~3→0, 4~6→1, ..., 19~21→6 */
KEY_ID_1629 = key;
/* 专业/普通(及对应设置页):记下最后指板和弦;专业同时记移调 */
{
uint8_t tab = (uint8_t)mGuiData[GUI_TAB_INDEX].Current;
uint8_t last = (uint8_t)mGuiData[GUI_TAB_LAST_INDEX].Current;
if (tab == 1 || (tab == 3 && last == 1))
{
Accomp_RememberTranspose();
Accomp_RememberLastFretChord(key);
}
else if (tab == 2 || (tab == 3 && last == 2))
Accomp_RememberLastFretChord(key);
}
MIDI = (STRING_MIDI *)Chord_Midi_Table[group];
ORGAN_MIDI = OrganChord_Midi_Table[group];
uint8_t type = chord_type_index_map[key].type;
switch(chord_type_index_map[key].PitchOffset)
{
case 1:
USE_MIDI.Midi_1 = ORGAN_MIDI[type].Midi_1;
USE_MIDI.Midi_2 = ORGAN_MIDI[type].Midi_2;
USE_MIDI.Midi_3 = ORGAN_MIDI[type].Midi_3;
USE_MIDI.Midi_4 = ORGAN_MIDI[type].Midi_4;
USE_MIDI.Midi_5 = ORGAN_MIDI[type].Midi_5;
USE_MIDI.Midi_6 = ORGAN_MIDI[type].Midi_6;
break;
case 2:
USE_MIDI.Midi_1 = MIDI[type].Midi_1;
USE_MIDI.Midi_2 = MIDI[type].Midi_2;
USE_MIDI.Midi_3 = MIDI[type].Midi_3;
USE_MIDI.Midi_4 = MIDI[type].Midi_4;
USE_MIDI.Midi_5 = MIDI[type].Midi_5;
USE_MIDI.Midi_6 = MIDI[type].Midi_6;
break;
}
USE_MIDI.Midi_1 += mGuiData[GUI_TRANSPOSE].Current;
USE_MIDI.Midi_2 += mGuiData[GUI_TRANSPOSE].Current;
USE_MIDI.Midi_3 += mGuiData[GUI_TRANSPOSE].Current;
USE_MIDI.Midi_4 += mGuiData[GUI_TRANSPOSE].Current;
USE_MIDI.Midi_5 += mGuiData[GUI_TRANSPOSE].Current;
USE_MIDI.Midi_6 += mGuiData[GUI_TRANSPOSE].Current;
if(mGuiData[GUI_TAB_INDEX].Current == 1 && StartFlag)
{
/* 不按 Last_Tm1926D_Key 去重:App 曲目相邻两节可能是同一和弦键,
* 松手再按同一键也必须能续奏(与「指板+拨片」一致,拨片本无去重) */
if (!Band_IsPlaying())
{
/* 节奏:段落自然停后指板续奏 → piece 重启。
* 本地曲:圆键切段后 PlayTick 常短暂停滞,若走 Stop+Start 会关音;
* 与节奏「播放中换把」一致,仅换和弦音持续播放。段落后续奏用拨片/再按圆键。 */
if (mGuiData[GUI_AUTOBAND_SW].Current)
{
uint8_t midi_note_buff[4] = {0,};
uint8_t note_cnt = GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff);
for(uint8_t i = 0;i < note_cnt;i++)
AutoBandTop1_Note_On(midi_note_buff[i],0x50);
Return_Light_buff(KEY_ID_1629);
Band_NotifyPlayActivity();
LOG_I("pick", "expert/fret change(local) chord=%u", (unsigned)KEY_ID_1629);
}
else
{
MainTask_Sendmsg(MSG_ID_BP_KEY, 0, 0, 0);
LOG_I("pick", "expert/fret continue chord=%u", (unsigned)KEY_ID_1629);
}
}
else
{
/* 播放中(节奏库/本地曲):仅换和弦音,不重启段落 */
uint8_t midi_note_buff[4] = {0,};
uint8_t note_cnt = GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff);
for(uint8_t i = 0;i < note_cnt;i++)
{
AutoBandTop1_Note_On(midi_note_buff[i],0x50);
}
Return_Light_buff(KEY_ID_1629);
LOG_I("pick", "expert/fret change chord=%u", (unsigned)KEY_ID_1629);
}
}
}
break;
}
}
void Return_Light_buff(uint8_t key_id_1629)
{
uint8_t buff[7] = {0xF0, 0x51, 0x04, 0x01, 0x01, 0x08, 0xF7};
switch(key_id_1629)
{
case 1: case 2: case 3: buff[3] = 0x00;break;
case 4: case 5: case 6: buff[3] = 0x01;break;
case 7: case 8: case 9: buff[3] = 0x02;break;
case 10:case 11:case 12: buff[3] = 0x03;break;
case 13:case 14:case 15: buff[3] = 0x04;break;
case 16:case 17:case 18: buff[3] = 0x05;break;
case 19:case 20:case 21: buff[3] = 0x06;break;
}
switch(chord_type_index_map[key_id_1629].type)
{
case 0:buff[5] = 0; break;
case 1:buff[5] = 8; break;
case 2:buff[5] = 10;break; /* m7 -> 0x0A */
case 3:buff[5] = 13;break; /* 7 -> 0x0D */
case 4:buff[5] = 2; break; /* M7 -> 0x02 */
case 5:buff[5] = 4; break; /* ad9 -> 0x04 */
case 6:buff[5] = 11;break; /* m7-5 -> 0x0B */
case 7:buff[5] = 31;break; /* sus2 -> 0x1F */
case 8:buff[5] = 30;break; /* sus4 -> 0x1E */
}
if(Led == buff[3])
USART4_SendData(buff,sizeof(buff));
}
void Pick_Handle()
{
uint8_t midi = 0;
uint8_t midi_note_buff[4] = {0,};
uint8_t note_cnt = 0;
uint8_t chord_id = KEY_ID_1629;
/* 未进入演奏模式(开机模式选择页)禁止起奏 */
if (!InModeFlag)
{
LOG_I("pick", "abort: not in mode");
return;
}
/* 空库/加载失败时禁止起奏:否则 TMR6 ISR 内 AutoBand 可能 HardFault,
* LCD 卡死且主循环 Power_Key_Scan 不再跑 → 只能 RESET */
if (!App_Auto_IsPresetReady())
{
LOG_E("pick", "abort: preset not ready tab=%u addr=0x%08X",
(unsigned)mGuiData[GUI_TAB_INDEX].Current, (unsigned)ADDRESS);
return;
}
/* 左手未触发和弦板(或落在拍速/停止键)时:拨片默认发当前调一级大和弦伴奏。
* 普通无指板也可起奏:用专业/普通记下的最后指板和弦,无记录才退回一级;无操作则偶拍停。 */
{
uint8_t free_pick_no_fret =
(mGuiData[GUI_TAB_INDEX].Current == 2 &&
(PressFlag == 0 || chord_id == 0 || chord_id > 21)) ? 1u : 0u;
if (free_pick_no_fret)
{
FreeMode_ApplyLastFretChordForNoFretPick();
chord_id = KEY_ID_1629;
}
else if (mGuiData[GUI_TAB_INDEX].Current != 2 &&
(chord_id == 0 || chord_id == 22 || chord_id == 23))
{
Accomp_ApplyDefaultIChord();
chord_id = KEY_ID_1629;
}
note_cnt = GetChordNotesByType(chord_id, chord_type_index_map[chord_id].type, midi_note_buff);
Return_Light_buff(KEY_ID_1629);
/* 首次起奏前补发主音量:防开机 SysEx 丢失后"图标低、实际大" */
if (StartFlag == 0)
MasterVolume_Reapply();
switch(mGuiData[GUI_TAB_INDEX].Current)
{
case 3:
switch(mGuiData[GUI_TAB_LAST_INDEX].Current)
{
case 2:
if(StartFlag == 0)//当播放了之后,拨片不再起作用
{
StartFlag = 1;
AutoBandTop1_SetLoopMode(0,1);
midi = 0x3C + mGuiData[GUI_TRANSPOSE].Current;
Band_StartLocalRetry(midi, midi_note_buff, note_cnt);
}
break;
case 1:
StartFlag = 1;
AutoBandTop1_SetPiecePlayMode(1,0,1920);
Band_StartPieceRetry(midi_note_buff, note_cnt);
AutoBandTop1_SetLoopMode(0,1);
break;
case 0:
/* 设置页内沿用万能:拨片先前奏,再循环段落1 */
Univ_StartWithPreamble(midi_note_buff, note_cnt);
LOG_I("pick", "settings/universal preamble→sec1 name=%s notes=%u",
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)note_cnt);
break;
}
break;
case 2:
/* 普通:可无指板拨片起奏(一级+专业停键调性);有指板则用当前和弦。
* 无指板起奏后若无其它操作,偶拍末停;按住指板则循环至松手再偶拍停。
* 节奏用 piece;本地曲目用 StartWithNote */
{
uint8_t session_was_on = StartFlag ? 1u : 0u;
StartFlag = 1;
/* 普通:播放中指板 LED 全绿,关闭走向灯刷新 */
app_tm1629_all_on(LED_COLOR_G);
if (mGuiData[GUI_AUTOBAND_SW].Current)
{
/* 播放中再拨:Stop+Start 重启段落;停止键后已停则勿二次 Stop。
* 此 Stop 若真实停播会武装库的吞噬器 → 紧随吸收 */
if (session_was_on)
{ Band_MarkRealStop(); AutoBandTop1_Stop(); }
else
Band_StopBeforeColdStart();
AutoBandTop1_SetLoopMode(0,1);
AutoBandTop1_SetRunVar(mGuiData[GUI_PLAY_SECTION].Current);
midi = 0x3C + mGuiData[GUI_TRANSPOSE].Current;
Band_StartLocalRetry(midi, midi_note_buff, note_cnt);
LOG_I("pick", "local/normal root=%u name=%s noFret=%u", midi,
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)free_pick_no_fret);
}
else
{
AutoBandTop1_SetPiecePlayMode(1, 0, 1920);
Band_StartPieceRetry(midi_note_buff, note_cnt);
AutoBandTop1_SetLoopMode(0, 1);
LOG_I("pick", "normal/rhythm piece name=%s notes=%u chord=%u noFret=%u xp=%u",
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)note_cnt, (unsigned)chord_id,
(unsigned)free_pick_no_fret,
(unsigned)mGuiData[GUI_TRANSPOSE].Current);
}
/* 无指板:起奏后即武装偶拍停;有指板则等松手再 Arm */
if (free_pick_no_fret)
FreeMode_ArmEvenBeatStop();
}
break;
case 1:
/* 专业:播放中再拨片不 Stop+Start(库会把未消费的 Start 标志清掉 → 静音);
* 本地曲播放中仅换和弦音续播,节奏路径 piece 重启本就安全。 */
if (!s_start_followup_force &&
Band_IsPlaying() && mGuiData[GUI_AUTOBAND_SW].Current)
{
Accomp_UpdatePlayingChord();
LOG_I("pick", "expert/pick skip restart playing chord=%u", (unsigned)chord_id);
break;
}
Expert_StartWithChord(chord_id, midi_note_buff, note_cnt, "pick");
break;
case 0:
/* 万能:拨片先播前奏,结束后进 piece 段落1 并 loop(不循环前奏) */
Univ_StartWithPreamble(midi_note_buff, note_cnt);
LOG_I("pick", "universal preamble→sec1 name=%s press=%u notes=%u chord=%u",
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
(unsigned)PressFlag, (unsigned)note_cnt, (unsigned)chord_id);
break;
}
}
}
/* 指板灯本色:与 App 引导灯一致(含黄/紫/青/白合成色) */
static LedColor_TypeDef tm1629_led_identity_color(LedNum_TypeDef led)
{
switch (led)
{
case LED_L1: return LED_COLOR_R;
case LED_L2: return LED_COLOR_G;
case LED_L3: return LED_COLOR_B;
case LED_L4: return LED_COLOR_YELL;
case LED_L5: return LED_COLOR_PURP;
case LED_L6: return LED_COLOR_CYA;
case LED_L7: return LED_COLOR_WHITE;
default: return COLOR_OTHER;
}
}
static void tm1629_set_identity_led(LedNum_TypeDef led)
{
LedColor_TypeDef color = tm1629_led_identity_color(led);
if (color == COLOR_OTHER)
app_tm1629_all_off();
else
app_tm1629_set_led(led, color);
}
void Loop_Light_Handle(uint8_t measure)
{
switch(ParamGuiData[ALL_MODE_PARAM].Current)
{
case 0:
switch(measure)
{
case 1:tm1629_set_identity_led(LED_L1);break;
case 2:tm1629_set_identity_led(LED_L6);break;
case 3:tm1629_set_identity_led(LED_L4);break;
case 4:tm1629_set_identity_led(LED_L5);break;
}
break;
case 1:
switch(measure)
{
case 1:tm1629_set_identity_led(LED_L1);break;
case 2:tm1629_set_identity_led(LED_L5);break;
case 3:tm1629_set_identity_led(LED_L6);break;
case 4:tm1629_set_identity_led(LED_L4);break;
}
break;
case 2:
switch(measure)
{
case 1:tm1629_set_identity_led(LED_L1);break;
case 2:tm1629_set_identity_led(LED_L3);break;
case 3:tm1629_set_identity_led(LED_L6);break;
case 4:tm1629_set_identity_led(LED_L4);break;
}
break;
}
}
void BL_Set_led(uint8_t led, uint8_t chord_type)
{
/* 问题2修复:曲目 LED 引导只负责点灯,禁止改写和弦映射表。
* 否则 App 曲谱显示 6m(Am) 时若 LED 帧带 0x02,会把 VI 中间键写成 AM7,
* 导致 UI 与 MIDI 发声不一致;和弦品质仅由映射页 02 03/02 04 维护。 */
(void)chord_type;
Led = led;
tm1629_set_identity_led((LedNum_TypeDef)led);
}
void BT_Pitch_offset_map(uint8_t pos_offset,uint8_t pitch_offset)
{
if (pos_offset < 1u || pos_offset > 21u)
return;
chord_type_index_map[pos_offset].PitchOffset = pitch_offset;
}
void BT_Chord_offset_map(uint8_t pos_offset,uint8_t chord_offset)
{
//char Info[20];
STRING_MIDI *MIDI;
const STRING_MIDI *ORGAN_MIDI;
uint8_t group;
uint8_t chord_APP_Local_offset = 0;
if (pos_offset < 1u || pos_offset > 21u)
return;
group = (pos_offset - 1) / 3; /* 1~3→0, 4~6→1, ..., 19~21→6 */
switch(chord_offset){
case 0x00:
chord_APP_Local_offset = 0;
break;
case 0x08:
chord_APP_Local_offset = 1;
break;
case 0x02:
chord_APP_Local_offset = 4;
break;
case 0x0D: /* 属七(文档码) */
case 0x13: /* 属七(旧码兼容) */
chord_APP_Local_offset = 3;
break;
case 0x0A: //A
chord_APP_Local_offset = 2;
break;
case 0x1F: //1F
chord_APP_Local_offset = 7;
break;
case 0x1E: //1E
chord_APP_Local_offset = 8;
break;
case 0x04:
chord_APP_Local_offset = 5;
break;
case 0x0B: //B
chord_APP_Local_offset = 6;
break;
}
//sprintf (Info, "%d %d %d %d", chord_offset,chord_APP_Local_offset,pos_offset,chord_type_index_map[pos_offset].PitchOffset);
//LCD_ShowString(2, 176+16, (const uint8_t*)Info, RED, WHITE, 16, 0);
chord_type_index_map[pos_offset].type = chord_APP_Local_offset;
MIDI = (STRING_MIDI *)Chord_Midi_Table[group];
ORGAN_MIDI = OrganChord_Midi_Table[group];
switch(chord_type_index_map[pos_offset].PitchOffset){
case 1:
MIDI->Midi_1 = ORGAN_MIDI->Midi_1;
MIDI->Midi_2 = ORGAN_MIDI->Midi_2;
MIDI->Midi_3 = ORGAN_MIDI->Midi_3;
MIDI->Midi_4 = ORGAN_MIDI->Midi_4;
MIDI->Midi_5 = ORGAN_MIDI->Midi_5;
MIDI->Midi_6 = ORGAN_MIDI->Midi_6;
break;
case 2:
MIDI->Midi_1 = ORGAN_MIDI->Midi_1 + 1;
MIDI->Midi_2 = ORGAN_MIDI->Midi_2 + 1;
MIDI->Midi_3 = ORGAN_MIDI->Midi_3 + 1;
MIDI->Midi_4 = ORGAN_MIDI->Midi_4 + 1;
MIDI->Midi_5 = ORGAN_MIDI->Midi_5 + 1;
MIDI->Midi_6 = ORGAN_MIDI->Midi_6 + 1;
break;
}
}
uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff)
{
uint8_t n = 0;
uint8_t i;
uint8_t root;
buff[0] = buff[1] = buff[2] = buff[3] = 0;
switch(MIDI_Index)
{
case 1:case 2:case 3:
switch(type)
{
case 0:
buff[0] = 48;buff[1] = 52;buff[2] = 55;
break;
case 1:
buff[0] = 48;buff[1] = 51;buff[2] = 55;
break;
case 2:
buff[0] = 48;buff[1] = 51;buff[2] = 55;buff[3] = 58;
break;
case 3:
buff[0] = 48;buff[1] = 52;buff[2] = 55;buff[3] = 58;
break;
case 4:
buff[0] = 48;buff[1] = 52;buff[2] = 55;buff[3] = 59;
break;
case 5:
buff[0] = 48;buff[1] = 52;buff[2] = 55;buff[3] = 62;
break;
case 6:
buff[0] = 48;buff[1] = 51;buff[2] = 54;buff[3] = 58;
break;
case 7:
buff[0] = 48;buff[1] = 50;buff[2] = 55;
break;
case 8:
buff[0] = 48;buff[1] = 53;buff[2] = 55;
break;
}
break;
case 4:case 5:case 6:
switch(type)
{
case 0:
buff[0] = 50;buff[1] = 54;buff[2] = 57;
break;
case 1:
buff[0] = 50;buff[1] = 53;buff[2] = 57;
break;
case 2:
buff[0] = 50;buff[1] = 53;buff[2] = 57;buff[3] = 60;
break;
case 3:
buff[0] = 50;buff[1] = 54;buff[2] = 57;buff[3] = 60;
break;
case 4:
buff[0] = 50;buff[1] = 54;buff[2] = 57;buff[3] = 61;
break;
case 5:
buff[0] = 50;buff[1] = 54;buff[2] = 57;buff[3] = 64;
break;
case 6:
buff[0] = 50;buff[1] = 53;buff[2] = 56;buff[3] = 60;
break;
case 7:
buff[0] = 50;buff[1] = 52;buff[2] = 57;
break;
case 8:
buff[0] = 50;buff[1] = 55;buff[2] = 57;
break;
}
break;
case 7:case 8:case 9:
switch(type)
{
case 0:
buff[0] = 52;buff[1] = 56;buff[2] = 59;
break;
case 1:
buff[0] = 52;buff[1] = 55;buff[2] = 59;
break;
case 2:
buff[0] = 52;buff[1] = 55;buff[2] = 59;buff[3] = 62;
break;
case 3:
buff[0] = 52;buff[1] = 56;buff[2] = 59;buff[3] = 62;
break;
case 4:
buff[0] = 52;buff[1] = 56;buff[2] = 59;buff[3] = 63;
break;
case 5:
buff[0] = 52;buff[1] = 56;buff[2] = 59;buff[3] = 66;
break;
case 6:
buff[0] = 52;buff[1] = 55;buff[2] = 58;buff[3] = 62;
break;
case 7:
buff[0] = 52;buff[1] = 54;buff[2] = 59;
break;
case 8:
buff[0] = 52;buff[1] = 57;buff[2] = 59;
break;
}
break;
case 10:case 11:case 12:
switch(type)
{
case 0:
buff[0] = 53;buff[1] = 57;buff[2] = 60;
break;
case 1:
buff[0] = 53;buff[1] = 56;buff[2] = 60;
break;
case 2:
buff[0] = 53;buff[1] = 56;buff[2] = 60;buff[3] = 63;
break;
case 3:
buff[0] = 53;buff[1] = 57;buff[2] = 60;buff[3] = 63;
break;
case 4:
buff[0] = 53;buff[1] = 57;buff[2] = 60;buff[3] = 64;
break;
case 5:
buff[0] = 53;buff[1] = 57;buff[2] = 60;buff[3] = 67;
break;
case 6:
buff[0] = 53;buff[1] = 56;buff[2] = 59;buff[3] = 63;
break;
case 7:
buff[0] = 53;buff[1] = 55;buff[2] = 60;
break;
case 8:
buff[0] = 53;buff[1] = 58;buff[2] = 60;
break;
}
break;
case 13:case 14:case 15:
switch(type)
{
case 0:
buff[0] = 43;buff[1] = 47;buff[2] = 50;
break;
case 1:
buff[0] = 43;buff[1] = 46;buff[2] = 50;
break;
case 2:
buff[0] = 43;buff[1] = 46;buff[2] = 50;buff[3] = 53;
break;
case 3:
buff[0] = 43;buff[1] = 47;buff[2] = 50;buff[3] = 53;
break;
case 4:
buff[0] = 43;buff[1] = 47;buff[2] = 50;buff[3] = 54;
break;
case 5:
buff[0] = 43;buff[1] = 47;buff[2] = 50;buff[3] = 57;
break;
case 6:
buff[0] = 43;buff[1] = 46;buff[2] = 49;buff[3] = 53;
break;
case 7:
buff[0] = 43;buff[1] = 45;buff[2] = 50;
break;
case 8:
buff[0] = 43;buff[1] = 48;buff[2] = 50;
break;
}
break;
case 16:case 17:case 18:
switch(type)
{
case 0:
buff[0] = 45;buff[1] = 49;buff[2] = 52;
break;
case 1:
buff[0] = 45;buff[1] = 48;buff[2] = 52;
break;
case 2:
buff[0] = 45;buff[1] = 48;buff[2] = 52;buff[3] = 55;
break;
case 3:
buff[0] = 45;buff[1] = 49;buff[2] = 52;buff[3] = 55;
break;
case 4:
buff[0] = 45;buff[1] = 49;buff[2] = 52;buff[3] = 56;
break;
case 5:
buff[0] = 45;buff[1] = 49;buff[2] = 52;buff[3] = 59;
break;
case 6:
buff[0] = 45;buff[1] = 48;buff[2] = 51;buff[3] = 55;
break;
case 7:
buff[0] = 45;buff[1] = 47;buff[2] = 52;
break;
case 8:
buff[0] = 45;buff[1] = 50;buff[2] = 52;
break;
}
break;
case 19:case 20:case 21:
switch(type)
{
case 0:
buff[0] = 47;buff[1] = 51;buff[2] = 53;
break;
case 1:
buff[0] = 47;buff[1] = 50;buff[2] = 54;
break;
case 2:
buff[0] = 47;buff[1] = 50;buff[2] = 54;buff[3] = 57;
break;
case 3:
buff[0] = 47;buff[1] = 50;buff[2] = 53;buff[3] = 57;
break;
case 4:
buff[0] = 47;buff[1] = 51;buff[2] = 53;buff[3] = 57;
break;
case 5:
buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 58;
break;
case 6:
buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 60;
break;
case 7:
buff[0] = 47;buff[1] = 49;buff[2] = 54;
break;
case 8:
buff[0] = 47;buff[1] = 52;buff[2] = 54;
break;
case 9:
buff[0] = 47;buff[1] = 50;buff[2] = 53;
break;
}
break;
default:
return 0;
}
n = (buff[3] != 0) ? 4 : 3;
switch (chord_type_index_map[MIDI_Index].PitchOffset)
{
case 2: /* # 升:整体 +1 半音 */
for (i = 0; i < n; i++)
buff[i] += 1;
break;
default:
break;
}
for (i = 0; i < n; i++)
buff[i] += mGuiData[GUI_TRANSPOSE].Current;
/* 音师:仅所有调的 #四级(键 10~12 + 升)— 整组低八度,音域压在一个八度内
* 最低音 = #IV,最高音 ≤ 自然 IV。勿对六级等其它升号和弦套用。 */
if (MIDI_Index >= 10 && MIDI_Index <= 12 &&
chord_type_index_map[MIDI_Index].PitchOffset == 2)
{
for (i = 0; i < n; i++)
{
if (buff[i] >= 12)
buff[i] -= 12;
}
root = buff[0];
for (i = 1; i < n; i++)
{
uint8_t guard;
for (guard = 0; guard < 2 && buff[i] >= (uint8_t)(root + 12); guard++)
buff[i] -= 12;
for (guard = 0; guard < 2 && buff[i] < root; guard++)
buff[i] += 12;
}
}
return n;
}
void Read_Cur_Measure()
{
uint32_t now = rt_tick_get();
static uint32_t last_tick_i = 0;
uint8_t tab = (uint8_t)mGuiData[GUI_TAB_INDEX].Current;
uint8_t last = (uint8_t)mGuiData[GUI_TAB_LAST_INDEX].Current;
/* 仅万能(0)按和弦走向同步亮灯;普通(2)播放时保持全绿,不跟节奏换灯/换位。
* 设置页(3)仅当从万能进入时继续走向灯。 */
uint8_t loop_light_ok =
(tab == 0) ||
(tab == 3 && last == 0);
Band_TrackPlayTick();
FreeMode_PollEvenBeatStop();
Univ_PollEnterPieceAfterPreamble();
/* 尾奏中保持全绿,不刷新和弦走向红灯 */
if (loop_light_ok && StartFlag && !s_outro_led_all_green)
{
if(now - last_tick_i >= 10)
{
last_tick_i = now;
uint8_t Measure = AutoBandTop1_GetCurrentRunMeasure();
MainTask_Sendmsg(MSG_ID_LOOP_LIGHT, Measure, 0, 0);
}
}
}