#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 ?? �Զ����࿪�� 0=�� 1=�� { 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 ?? �Զ����࿪�� 0=�� 1=�� { 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,22}; /* major.minor.patch — 0.2.22: 普通/专业模式前奏间奏尾奏断奏即刻切入;万能模式忽略这四键 */ 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); 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); /* 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; 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; /* 更新关机标志 */ 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; } 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; /* libAutoBand m_ABSystem:+0x59 舞台,+0x50 小节相位。 * 前奏挂起舞台 0x0E、尾奏挂起舞台 0x0B 都要等相位对上小节线才真正切入。 */ 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_POST_WAIT 0x0Bu #define AB_STAGE_POST 0x0Cu static void FreeMode_CancelEvenBeatStop(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; } /* 尾奏起奏: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; 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; /* 万能:前奏/间奏/尾奏/断奏无响应(含 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 uint8_t s_free_even_stop_arm = 0; static uint64_t s_free_even_stop_tick = 0; static int s_free_even_stop_beat = 2; /* 目标偶拍 2 或 4 */ static uint8_t s_free_even_seen_target = 0; static void FreeMode_CancelEvenBeatStop(void) { s_free_even_stop_arm = 0; s_free_even_stop_tick = 0; s_free_even_stop_beat = 2; s_free_even_seen_target = 0; } /* 再次按住指板:取消待停并恢复 piece 循环 */ static void FreeMode_ResumePieceLoop(void) { if (!s_free_even_stop_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 = target; s_free_even_seen_target = (beat >= target) ? 1u : 0u; /* ((beat0/2)+1)*2*480 → 拍1/2→960,拍3/4→1920,跨小节同理 */ s_free_even_stop_tick = ((play / FREE_BEAT_TICKS) / 2ull + 1ull) * 2ull * (uint64_t)FREE_BEAT_TICKS; s_free_even_stop_arm = 1; /* 禁循环并裁 piece,库播到 stop tick 自停;Poll 作兜底 */ AutoBandTop1_SetLoopMode(0, 0); if (s_free_even_stop_tick <= 0xFFFFFFFFull) AutoBandTop1_SetPiecePlayMode(1, 0, (uint32_t)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_stop_arm) return; if (PressFlag || mGuiData[GUI_TAB_INDEX].Current != 2 || !StartFlag) { FreeMode_CancelEvenBeatStop(); return; } beat = AutoBandTop1_GetCurrentBeat(); if (beat == s_free_even_stop_beat) s_free_even_seen_target = 1; /* 已到达目标偶拍后离开该拍 = 该拍播完;或 play tick 到点 */ beat_done = (s_free_even_seen_target && beat != 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) { FreeMode_CancelEvenBeatStop(); Band_CancelStartFollowup(); Band_TickGate = 0; StartFlag = 0; KEY_ID_1629 = 0; Band_MarkCleanStopped(); Band_AllNotesOff(); s_outro_led_all_green = 0; for (uint8_t i = 0; i < 4; i++) key_toggle[i] = 0; } void Stop_AutoBand() { 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; //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; 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; /* 空库/加载失败时禁止起奏:否则 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; } /* 左手未触发和弦板(或落在拍速/停止键)时:拨片默认发当前调一级大和弦伴奏; * 普通模式除外——必须左手按住指板再拨,不做一级兜底(说明书需求) */ 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: /* 设置页内沿用万能:与主路径一致,始终 loop */ AutoBandTop1_SetPiecePlayMode(1, 0, 480); Band_StartPieceRetry(midi_note_buff, note_cnt); AutoBandTop1_SetLoopMode(0, 1); StartFlag = 1; break; } break; case 2: /* 普通:左手按住指板 + 拨片起奏;松手由 TM1629 停。 * 节奏用 piece;本地曲目用 StartWithNote */ if (PressFlag == 0 || chord_id == 0 || chord_id > 21) { LOG_I("pick", "normal skip press=%u chord=%u (need fretboard)", (unsigned)PressFlag, (unsigned)chord_id); break; } { 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", midi, AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?"); } 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", AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", (unsigned)note_cnt, (unsigned)chord_id); } } 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 模式;须 SetLoopMode,否则无循环时常听不到伴奏 */ StartFlag = 1; s_outro_led_all_green = 0; /* 拨片重开 piece,恢复走向灯 */ AutoBandTop1_SetPiecePlayMode(1, 0, 480); Band_StartPieceRetry(midi_note_buff, note_cnt); AutoBandTop1_SetLoopMode(0, 1); LOG_I("pick", "universal piece 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(); /* 尾奏中保持全绿,不刷新和弦走向红灯 */ 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); } } }