2423 lines
78 KiB
C
2423 lines
78 KiB
C
#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><>
|
||
{ TIMBRE_BOOT_SLOT, 0, 4, GUI_TIMBRE_SELECT}, // 3 显示第 1 项,Bank1 Program 24
|
||
{ 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,54}; /* major.minor.patch — 0.2.54: 进设置不停播,物理键保持原模式且不改设置页 */
|
||
uint8_t Led = 8;
|
||
/* App 已下发琴颈引导灯:拨片勿再全绿盖掉 */
|
||
static uint8_t s_app_neck_led = 0;
|
||
|
||
void AppNeckLed_NoteGuide(void)
|
||
{
|
||
s_app_neck_led = 1;
|
||
}
|
||
|
||
void AppNeckLed_ClearGuide(void)
|
||
{
|
||
uint8_t tab;
|
||
s_app_neck_led = 0;
|
||
/* 断开 App 后,普通/专业回到一直全绿 */
|
||
tab = (uint8_t)mGuiData[GUI_TAB_INDEX].Current;
|
||
if (tab == 1u || tab == 2u)
|
||
app_tm1629_all_on(LED_COLOR_G);
|
||
}
|
||
|
||
uint8_t AppNeckLed_IsGuide(void)
|
||
{
|
||
return s_app_neck_led;
|
||
}
|
||
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();
|
||
}
|
||
|
||
/* 音源板初始化完成前为 0。彩灯与上电并行,模式键在置 1 之前忽略 */
|
||
static volatile uint8_t s_audio_bringup_req = 0;
|
||
static volatile uint8_t s_audio_bringup_started = 0;
|
||
static volatile uint8_t s_audio_ready = 0;
|
||
|
||
void System_PowerOn(void)
|
||
{
|
||
LOG_I("PWR", "power_on begin");
|
||
/* 软关机不复位 MCU:清掉上次会话演奏状态,默认当前调一级大和弦(C 调=do mi sol) */
|
||
StartFlag = 0;
|
||
InModeFlag = false; /* 停在模式选择,禁止拨片/侧面键起奏 */
|
||
s_audio_ready = 0;
|
||
s_audio_bringup_started = 0;
|
||
s_audio_bringup_req = 0;
|
||
/* 弦音色、移调跨开关机保留。节奏/曲目/走向/段落/蓝牙/BPM 每次开机回默认 */
|
||
Session_ApplyPowerOnDefaults();
|
||
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 堵死触摸。
|
||
* 彩灯约 1.1s 与音源板上电重叠。s_audio_ready 置位前不进演奏模式。 */
|
||
void System_RequestAudioBringUp(void)
|
||
{
|
||
/* 保护/低电时充电电流很小,Dream+12V+功放会把包电压按住,充不上去 */
|
||
if (s_audio_bringup_started || s_audio_ready)
|
||
return;
|
||
if (!app_adc_charge_ui_allowed())
|
||
return;
|
||
s_audio_bringup_started = 1;
|
||
s_audio_ready = 0;
|
||
s_audio_bringup_req = 1;
|
||
}
|
||
|
||
uint8_t Audio_IsReady(void)
|
||
{
|
||
return s_audio_ready;
|
||
}
|
||
|
||
uint8_t Audio_BringupStarted(void)
|
||
{
|
||
return s_audio_bringup_started;
|
||
}
|
||
|
||
void Audio_AbortIfNotReady(void)
|
||
{
|
||
if (s_audio_ready)
|
||
return;
|
||
s_audio_bringup_req = 0;
|
||
s_audio_bringup_started = 0;
|
||
}
|
||
|
||
/* 低于等于 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;
|
||
if (!app_adc_charge_ui_allowed())
|
||
{
|
||
Audio_AbortIfNotReady();
|
||
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");
|
||
/* 12V 一开功放默认不静音,会有一声啪。数字电源起来后先静音,再等时钟和模拟电源 */
|
||
rt_thread_mdelay(20);
|
||
AD85020B_MasterMute(1);
|
||
rt_thread_mdelay(460);
|
||
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();
|
||
if (!powon || !app_adc_charge_ui_allowed())
|
||
{
|
||
Audio_AbortIfNotReady();
|
||
BSP_DreamCorePowerEnable(0);
|
||
BSP_HT7178PowerEnable(0);
|
||
BSP_BlueToothPowerEnable(0);
|
||
return;
|
||
}
|
||
s_audio_ready = 1;
|
||
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; /* 更新关机标志 */
|
||
s_audio_ready = 0;
|
||
s_audio_bringup_started = 0;
|
||
s_audio_bringup_req = 0;
|
||
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=等尾奏播完再停,bit1=已见到尾奏舞台 */
|
||
static uint8_t s_outro_wait_end = 0;
|
||
#define OUTRO_WAIT_END 0x01u
|
||
#define OUTRO_SAW_STAGE 0x02u
|
||
|
||
/* 万能拨片: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;
|
||
}
|
||
|
||
/* 万能 1/2/3 才是加花;4 号是尾奏,不占用屏幕焦点 */
|
||
static uint8_t Univ_FillSection(void)
|
||
{
|
||
uint8_t sec = (uint8_t)mGuiData[GUI_PLAY_SECTION].Current;
|
||
if (sec > 2u)
|
||
sec = 2u;
|
||
return sec;
|
||
}
|
||
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;
|
||
s_outro_wait_end = 0;
|
||
/* 再拨片按屏幕上仍选中的 1/2/3 加花,不改回段落 1 */
|
||
|
||
if (session_was_on)
|
||
AutoBandTop1_Stop();
|
||
else
|
||
Band_StopBeforeColdStart();
|
||
|
||
/* 须先退出 piece,否则 Preamble 舞台切不过去(同尾奏) */
|
||
AutoBandTop1_SetPiecePlayMode(0, 0, 0);
|
||
AutoBandTop1_SetRunVar(Univ_FillSection());
|
||
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(Univ_FillSection());
|
||
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);
|
||
}
|
||
|
||
/* 万能 4 号尾奏播完后硬停,状态与止音键一致,拨片才能再启动 */
|
||
static void Univ_PollOutroEnd(void)
|
||
{
|
||
uint8_t st;
|
||
uint8_t tab;
|
||
uint8_t last;
|
||
|
||
if (!(s_outro_wait_end & OUTRO_WAIT_END) || !StartFlag)
|
||
return;
|
||
tab = (uint8_t)mGuiData[GUI_TAB_INDEX].Current;
|
||
last = (uint8_t)mGuiData[GUI_TAB_LAST_INDEX].Current;
|
||
if (tab != 0u && !(tab == 3u && last == 0u))
|
||
{
|
||
s_outro_wait_end = 0;
|
||
return;
|
||
}
|
||
st = m_ABSystem[AB_STAGE_OFF];
|
||
if (st == AB_STAGE_POST_WAIT || st == AB_STAGE_POST)
|
||
{
|
||
s_outro_wait_end |= OUTRO_SAW_STAGE;
|
||
return;
|
||
}
|
||
if (s_outro_wait_end & OUTRO_SAW_STAGE)
|
||
{
|
||
s_outro_wait_end = 0;
|
||
Stop_AutoBand_Request();
|
||
}
|
||
}
|
||
|
||
/* 尾奏起奏: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);
|
||
/* 尾奏:未连 App 时指板全绿。播完后 Univ_PollOutroEnd 硬停。 */
|
||
s_outro_led_all_green = 1;
|
||
s_outro_wait_end = OUTRO_WAIT_END;
|
||
if (!AppNeckLed_IsGuide())
|
||
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 || !Audio_IsReady())
|
||
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;
|
||
/* 设置入口及子页:加花键保持进入前模式的段落/尾奏,不得假触摸点进系统设置或恢复出厂 */
|
||
if (UI_IsSettingsPage())
|
||
{
|
||
ResetAutoPowerCount();
|
||
if (key <= 3u)
|
||
UI_PlaySectionKey(key);
|
||
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_outro_wait_end = 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;
|
||
}
|
||
if (!Audio_IsReady())
|
||
{
|
||
LOG_I("pick", "abort: audio not ready");
|
||
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;
|
||
}
|
||
|
||
/* 万能(及设置页沿用万能):拨片只负责启动。播放行进中再拨不换和弦、不重开前奏。 */
|
||
if (StartFlag &&
|
||
(mGuiData[GUI_TAB_INDEX].Current == 0 ||
|
||
(mGuiData[GUI_TAB_INDEX].Current == 3 &&
|
||
mGuiData[GUI_TAB_LAST_INDEX].Current == 0)))
|
||
{
|
||
LOG_I("pick", "universal skip: already started");
|
||
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 弹唱正在引导时不要盖成全绿。 */
|
||
if (!AppNeckLed_IsGuide())
|
||
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:
|
||
/* 专业:未连 App 时把位灯一直全绿。App 引导中不改灯。
|
||
* 播放中再拨片不 Stop+Start(库会把未消费的 Start 标志清掉 → 静音);
|
||
* 本地曲播放中仅换和弦音续播,节奏路径 piece 重启本就安全。 */
|
||
if (!AppNeckLed_IsGuide())
|
||
app_tm1629_all_on(LED_COLOR_G);
|
||
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;
|
||
AppNeckLed_NoteGuide();
|
||
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();
|
||
Univ_PollOutroEnd();
|
||
|
||
/* 尾奏中保持全绿。App 弹唱只显示下发灯,不走本地走向灯。 */
|
||
if (loop_light_ok && StartFlag && !s_outro_led_all_green && !AppNeckLed_IsGuide())
|
||
{
|
||
if(now - last_tick_i >= 10)
|
||
{
|
||
last_tick_i = now;
|
||
uint8_t Measure = AutoBandTop1_GetCurrentRunMeasure();
|
||
MainTask_Sendmsg(MSG_ID_LOOP_LIGHT, Measure, 0, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|