K1Guitar/Global/Global.c

2277 lines
74 KiB
C
Raw Normal View History

#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_CARD_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_CARD_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 网格同步 */
{ UI0902_CARD_X, (uint16_t)(UI0902_CARD_X + UI0902_CARD_W / 2 - 1),
UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, -1, 0, GUI_TRANSPOSE, NULL },
{ (uint16_t)(UI0902_CARD_X + UI0902_CARD_W / 2), (uint16_t)(UI0902_CARD_X + UI0902_CARD_W - 1),
UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, 1, 0, GUI_TRANSPOSE, NULL },
{ (uint16_t)(UI0902_CARD_X + UI0902_CARD_W + UI0902_CARD_GAP),
(uint16_t)(UI0902_CARD_X + UI0902_CARD_W + UI0902_CARD_GAP + UI0902_CARD_W / 2 - 1),
UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, -1, 0, GUI_SPEED, NULL },
{ (uint16_t)(UI0902_CARD_X + UI0902_CARD_W + UI0902_CARD_GAP + UI0902_CARD_W / 2),
(uint16_t)(UI0902_CARD_X + UI0902_CARD_W * 2 + UI0902_CARD_GAP - 1),
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,35}; /* major.minor.patch — 0.2.35: 12V稳定后再初始化功放,避免开机无声 */
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;已回退硬停,保持为 1) */
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.8V:提示界面停留后关机(插着 Type-C 也停负载) */
#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();
/* 彩灯期间只留主电和侧灯。12V 升压、Dream、功放、蓝牙在 Logo 彩灯结束后再开 */
BSP_MainPowerEnable(1);
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
powon = true; /* 更新开机标志 */
/* 侧灯驱动先就绪,彩灯由 UI 在 Logo 画面上播放 */
TM1629D_Key_LED_Init();
app_tm1617_init();
LOG_I("PWR", "send POWER_ON (logo+led, audio rails off)");
MainTask_Sendmsg(MSG_ID_POWER_ON, 0, 0, 0);
}
void Power_LogRails(const char *where)
{
unsigned main_on = (unsigned)(gpio_output_data_bit_read(S_PWR_ON_OFF_GPIO_Port, S_PWR_ON_OFF_Pin) == SET);
unsigned dream_on = (unsigned)(gpio_output_data_bit_read(P_CORE_PWR_GPIO_Port, P_CORE_PWR_Pin) == SET);
unsigned v12_on = (unsigned)(gpio_output_data_bit_read(S_HT7178_EN_GPIO_Port, S_HT7178_EN_Pin) == SET);
unsigned bt_on = (unsigned)(gpio_output_data_bit_read(S_BT_PWR_GPIO_Port, S_BT_PWR_Pin) == SET);
/* 充电使能低有效:引脚为低记 chg=1 */
unsigned chg_on = (unsigned)(gpio_output_data_bit_read(S_CHARGE_CTRL_GPIO_Port, S_CHARGE_CTRL_Pin) == RESET);
unsigned bl_on = (unsigned)(gpio_output_data_bit_read(LCD_BLK_PORT, LCD_BLK_PIN) == SET);
LOG_I("PWR", "%s powon=%u main=%u dream=%u v12=%u bt=%u chg=%u bl=%u usb=%u pct=%u",
where ? where : "?",
(unsigned)powon, main_on, dream_on, v12_on, bt_on, chg_on, bl_on,
(unsigned)usb_charging_state, (unsigned)percentage);
}
/* 关机充电:只留主电给屏幕,其余外设必须关掉 */
static void Power_QuiesceForCharge(const char *where)
{
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
Power_LogRails(where);
}
/* UI 播完开机彩灯后请求;由主循环执行,避免功放 I2C 堵死触摸 */
static volatile uint8_t s_audio_bringup_req = 0;
void System_RequestAudioBringUp(void)
{
s_audio_bringup_req = 1;
}
/* 低于等于 6.8V:充电画面亮 10s 后关屏,充电继续 */
#define CHARGE_BL_LOW_MS 10000u
static uint32_t s_charge_bl_off_at = 0;
static uint8_t s_charge_bl_latched_off = 0;
/* 高于 6.8V:充电画面一直亮着 */
void Power_ChargeBacklightSteady(void)
{
s_charge_bl_latched_off = 0;
s_charge_bl_off_at = 0;
LCD_WR_REG(0x29);
LCD_BLK_Set();
}
/* 未高于 6.8V:只开始一次 10s 倒计时,电量刷新不能把它清掉或重新点亮 */
void Power_ChargeBacklightLowHold(void)
{
if (s_charge_bl_latched_off || s_charge_bl_off_at != 0u)
return;
LCD_WR_REG(0x29);
LCD_BLK_Set();
s_charge_bl_off_at = rt_tick_get() + (CHARGE_BL_LOW_MS * RT_TICK_PER_SECOND / 1000u);
LOG_I("PWR", "charge lcd on, off in %ums (pack <=6.8V)", (unsigned)CHARGE_BL_LOW_MS);
}
void Power_ChargeBacklightSessionReset(void)
{
s_charge_bl_off_at = 0;
s_charge_bl_latched_off = 0;
}
void Power_ChargeIdle_LogPoll(void)
{
static uint32_t s_last;
uint32_t now;
if (powon)
{
s_charge_bl_off_at = 0;
s_charge_bl_latched_off = 0;
return;
}
now = rt_tick_get();
if (s_charge_bl_off_at != 0u && (int32_t)(now - s_charge_bl_off_at) >= 0)
{
LCD_BLK_Clr();
LCD_WR_REG(0x28); /* Display OFF,充电不中断 */
s_charge_bl_off_at = 0;
s_charge_bl_latched_off = 1;
LOG_I("PWR", "charge lcd off after 10s, still charging");
}
if (s_last != 0u && (now - s_last) < (5u * RT_TICK_PER_SECOND))
return;
s_last = now;
Power_LogRails("charge-idle");
}
extern uint8_t level;
void System_AudioBringUp_Poll(void)
{
if (!s_audio_bringup_req || !powon)
return;
s_audio_bringup_req = 0;
LOG_I("PWR", "bring up audio rails");
/* LOGO 和彩灯期间不要求发音,电源在彩灯结束后才开。
12V 与 Dream 一起开:功放没有 I2S 时钟时写寄存器会停在静音。
等电源和时钟都稳后再初始化功放,并且只做一次,避免琴弦波形被第二次复位打乱。 */
BSP_HT7178PowerEnable(1);
BSP_DreamCorePowerEnable(1);
BSP_BlueToothPowerEnable((uint8_t)(mGuiData[GUI_BL_SW].Current ? 1 : 0));
Power_LogRails("audio-up");
rt_thread_mdelay(480);
if (!powon)
return;
AD85020B_Init();
App_Auto_Init();
/* App 协议走 UART4:须在开机后即解析,不能等到触摸选模式才 StartTask */
StartTask();
/* Dream 刚上电会丢掉首包。等它起来后再发弦音色和主音量,并补发一次 */
rt_thread_mdelay(400);
if (!powon)
return;
MasterVolume_Reapply();
LoadConfig();
rt_thread_mdelay(400);
if (!powon)
return;
MasterVolume_Reapply();
LoadConfig();
MainTask_Sendmsg(MSG_ID_EC_VOL, app_adc_get_volume(), level, 0);
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");
Power_QuiesceForCharge("soft-off-charge");
MainTask_Sendmsg(MSG_ID_POWOFF_CHARG, percentage, 1, 0);
}
else
{
Power_LogRails("soft-off");
}
}
}
/* 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.8V:提示界面停留 10s 后关机(插着 Type-C 也停负载) */
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)");
Power_QuiesceForCharge("usb-boot-charge");
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;
/* 伴奏播放中视为在使用,不倒计时;停奏后再从满额计时 */
if (StartFlag)
{
ResetAutoPowerCount();
s_last_sec_tick = rt_tick_get();
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)
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;
/* 侧面键按压/释放均为用户操作 */
ResetAutoPowerCount();
/* 模式选择页:侧面键不得起奏 */
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();
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)
{
/* 模式选择页:圆形段落键不得假触摸点选模式行,也不得改段落 */
if (!InModeFlag)
return;
/* 调音台:段落数字键不得注入假触摸(会打在推子底部,把数值打到 1) */
if (CurrUIProcress == UI_Mixer_Process)
return;
ResetAutoPowerCount();
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 拨片并发。 */
void Stop_AutoBand_Request(void)
{
/* ID=0:硬停(AutoBandTop1_Stop)。软停已回退:库态与 UI 态必须一致。 */
MainTask_Sendmsg(MSG_ID_STOP_BAND, 0, 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));
}
}
/* 兼容旧入口:一律硬停,不再做「只静音不停库」的软停。 */
void Stop_AutoBand_Soft(void)
{
Stop_AutoBand();
}
void Stop_AutoBand(void)
{
Expert_RememberStopContext();
FreeMode_CancelEvenBeatStop();
Band_CancelStartFollowup();
Band_TickGate = 1; /* 始终喂库节拍;软停曾清 0,已撤销 */
StartFlag = 0;
KEY_ID_1629 = 0;
Band_MarkCleanStopped();
AutoBandTop1_Stop();
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;
}
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 */
static void Band_MarkCleanStopped(void)
{
s_play_tick_last = 0;
s_play_adv_tick = 0;
}
/* 软停/吞噬器/补发起奏已撤销:保留空实现,避免其它调用点改动。 */
void Band_MarkRealStop(void)
{
}
void Band_CancelStartFollowup(void)
{
}
uint8_t Band_TakeStartFollowup(void)
{
return 0;
}
void Band_BeginStartFollowup(void)
{
}
void Band_EndStartFollowup(void)
{
}
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)
{
}
/* 冷启动前一律 Stop 清库残留(与软停前回退一致:不再「已软停则跳过」)。 */
static void Band_StopBeforeColdStart(void)
{
AutoBandTop1_Stop();
}
/* 诊断钩子:兼容 RTT `band dirty`(现冷启动本就会 Stop,保留空操作)。 */
void Band_ForceStopBeforeStart(void)
{
}
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;
/* 指板/拍速/停止键均为用户操作 */
ResetAutoPowerCount();
/* 万能:指板不改和弦、不启停伴奏;保留拍速/停止/拨片上推快移调 */
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;
/* 拨片为用户操作(含模式选择页误触):打断自动关机 */
ResetAutoPowerCount();
/* 未进入演奏模式(开机模式选择页)禁止起奏 */
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 清残留 */
if (session_was_on)
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 (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);
}
}
}