From d00f570e2fbfbe9e9f4e944024d066bf9aa16f35 Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Tue, 22 Sep 2026 17:42:03 +0800 Subject: [PATCH] fix(band): soft-stop on stop key so immediate pick can start Stop key no longer calls AutoBandTop1_Stop (library eats the next Start). Soft mute + tick gate runs on MainTask; cold start skips redundant Stop when already clean. Co-authored-by: Cursor --- APP/app_log.c | 27 ++++- Global/Global.c | 243 +++++++++++++++++++++++++++++++++++----- Global/Global.h | 14 ++- project/src/wk_system.c | 3 +- task/task_init.c | 18 +++ 5 files changed, 272 insertions(+), 33 deletions(-) diff --git a/APP/app_log.c b/APP/app_log.c index 656e071..de3ae7c 100644 --- a/APP/app_log.c +++ b/APP/app_log.c @@ -315,8 +315,31 @@ uint8_t app_log_try_command(const char *cmd) "FLASH_BIN3_GO", "FLASH_BIN3_OK\n"); return 1U; } - if (strncmp(cmd, "tone status", 11) == 0) { - uint8_t hdr[16]; + /* bp pick:经 MainTask 队列模拟真实拨片(与停止键串行,复现停后立即拨) */ + if (strncmp(cmd, "bp pick", 7) == 0) { + MainTask_Sendmsg(MSG_ID_BP_KEY, 1 /*PICK_STATE_DOWN*/, 0, 0); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "BP_PICK_OK\n"); + return 1U; + } + /* band dirty:强制下次冷启动先 Stop(验证 Stop→Start 紧挨是否必被吞) */ + if (strncmp(cmd, "band dirty", 10) == 0) { + Band_ForceStopBeforeStart(); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "BAND_DIRTY_OK\n"); + return 1U; + } + /* band state:停/起时序诊断 — StartFlag/PlayTick/是否在播 */ + if (strncmp(cmd, "band state", 10) == 0) { + char line[96]; + snprintf(line, sizeof(line), + "BAND_STATE start=%u playing=%u playtick=%lu ms=%lu\n", + (unsigned)StartFlag, + (unsigned)Band_IsPlaying(), + (unsigned long)AutoBandTop1_GetCurrentPlayTick(), + (unsigned long)app_log_ms()); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line); + return 1U; + } + if (strncmp(cmd, "tone status", 11) == 0) {uint8_t hdr[16]; uint8_t name[40]; uint8_t cur_hdr[16]; char *pname; diff --git a/Global/Global.c b/Global/Global.c index f37d4f5..5cd7b7c 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -66,6 +66,10 @@ Fader_t faders[4] = { const Touch_AreaTypeDef Touch_Areas[] = { + /* 状态栏蓝牙:点击开关(热区略大于图标) */ + { UI0902_BT_HIT_X0, UI0902_BT_HIT_X1, UI0902_BT_HIT_Y0, UI0902_BT_HIT_Y1, + 0, 0, GUI_BL_SW, NULL }, + /* Xmin,Xmax,Ymin,Ymax, Flag, Value, ID, action — 与 UI_Label 网格同步 */ { 12, 64, UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, -1, 0, GUI_TRANSPOSE, NULL }, { 65, 116, UI0902_CARD_Y, UI0902_CARD_Y + UI0902_CARD_H - 1, 1, 0, GUI_TRANSPOSE, NULL }, @@ -107,6 +111,14 @@ bool PressFlag = 0; void Return_Light_buff(uint8_t key_id_1629); uint8_t Band_IsPlaying(void); +void Band_AbsorbEatenStart(void); +void Band_MarkRealStop(void); +void Band_CancelStartFollowup(void); +static void Band_StartLocalRetry(uint8_t midi, uint8_t *notes, uint8_t n); +static void Band_StartPieceRetry(uint8_t *notes, uint8_t n); +static void Band_SyncStartRetry(void); +/* 1=喂 AutoBand 1ms 节拍;停止键软停时清 0,避免库继续出音且不必 Stop */ +uint8_t Band_TickGate = 1; uint8_t AutoCloseTime = 15; uint16_t mAutoPowerOffCount = 15 * 60; @@ -672,7 +684,7 @@ void AutoBand_StartOutro(void) app_tm1629_all_on(LED_COLOR_G); /* 尾奏起奏前补发主音量,保证与图标一致 */ MasterVolume_Reapply(); - AutoBandTop1_SyncStart(); + Band_SyncStartRetry(); wk_delay_ms(50); 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++) @@ -712,7 +724,7 @@ void ADC_IN1_KEY_Handle(uint8_t key,bool on) /* 前奏/断奏起奏前补发主音量,保证与图标一致 */ MasterVolume_Reapply(); //AutoBandTop1_Start(); - AutoBandTop1_SyncStart(); + Band_SyncStartRetry(); wk_delay_ms(50); 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++) @@ -888,25 +900,62 @@ void FreeMode_PollEvenBeatStop(void) if (tick_done || beat_done) { FreeMode_CancelEvenBeatStop(); - Stop_AutoBand(); + Stop_AutoBand_Request(); LOG_I("pick", "free even-stop done beat=%d tick=%u beatDone=%u", beat, (unsigned)tick_done, (unsigned)beat_done); } } +static void Band_MarkCleanStopped(void); +static void Band_StopBeforeColdStart(void); + +/* 扫描线程只投递:真正 Stop 在 MainTask,与拨片 Pick 同队列串行。 + * 若在扫描线程同步 Stop,会与 MainTask 拨片并发:Stop 期间 StartFlag 仍为 1, + * 拨片误判 Band_IsPlaying → 只改和弦不起奏,需再等 ~0.5s 才能拨响。 */ +void Stop_AutoBand_Request(void) +{ + /* ID=1:停止键软停(静音+停节拍,不调库 Stop,否则随后 Start 被吞) */ + MainTask_Sendmsg(MSG_ID_STOP_BAND, 1, 0, 0); +} + +static void Band_AllNotesOff(void) +{ + uint8_t i; + for (i = 0; i < 16; i++) + { + uint8_t AllNoteOff_buff[3] = {(uint8_t)(0xB0 | i), 0x7B, 0x00}; + SendMidiDataToDreamDSP(AllNoteOff_buff, sizeof(AllNoteOff_buff)); + uint8_t AllControlOff_buff[3] = {(uint8_t)(0xB0 | i), 0x79, 0x00}; + SendMidiDataToDreamDSP(AllControlOff_buff, sizeof(AllControlOff_buff)); + } +} + +/* 停止键/偶拍收束:立刻静音并暂停库 1ms 节拍,不调用 AutoBandTop1_Stop。 + * 库 Stop 之后第一个 Start 必被吞,立即拨片无声。 */ +void Stop_AutoBand_Soft(void) +{ + FreeMode_CancelEvenBeatStop(); + Band_CancelStartFollowup(); + Band_TickGate = 0; + StartFlag = 0; + KEY_ID_1629 = 0; + Band_MarkCleanStopped(); + Band_AllNotesOff(); + s_outro_led_all_green = 0; + for (uint8_t i = 0; i < 4; i++) key_toggle[i] = 0; +} + void Stop_AutoBand() { FreeMode_CancelEvenBeatStop(); - AutoBandTop1_Stop(); - for(uint8_t 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)); - } + /* 先清播放态,再 Stop/AllNoteOff,避免处理期间其它路径误判在播 */ + Band_CancelStartFollowup(); + Band_TickGate = 1; StartFlag = 0; - KEY_ID_1629 = 0; /* 停奏后再清,避免收束段音区映射突变 */ + KEY_ID_1629 = 0; + Band_MarkCleanStopped(); + AutoBandTop1_Stop(); + Band_AllNotesOff(); s_outro_led_all_green = 0; //tick_ever_positive = false; for(uint8_t i = 0;i < 4;i++) key_toggle[i] = 0; @@ -950,6 +999,135 @@ uint8_t BP_Transpose(uint8_t key_idx) * 供专业模式指板键区分「播放中换和弦」与「段落结束后续奏重启」。 */ static uint64_t s_play_tick_last = 0; static uint32_t s_play_adv_tick = 0; /* 最近一次 PlayTick 前进的系统 tick */ +/* 1=下次冷启动前需先 Stop 清库残留;0=Stop_AutoBand 已停干净,勿再 Stop。 */ +static uint8_t s_band_need_stop_before_start = 1; +/* 库特性(RTT 实测):真实停播后第一个完整 Start 被吞 + * (StartFlag=1 但 PlayTick 归 0 且不前进);第二次完整 Start 才起奏。 + * 哑 Start 不能用来吸收,会占住会话把真正起奏也吃掉。 */ +static uint8_t s_band_eater_armed = 0; + +static void Band_MarkCleanStopped(void) +{ + s_play_tick_last = 0; + s_play_adv_tick = 0; + s_band_need_stop_before_start = 0; +} + +/* 一切可能真实停播的 Stop 路径调用:武装「停后首启被吞」标记。 + * 宁可误武装(哑 Start 未被吞也只会被紧随的真实 Start 立刻重触发,无感), + * 不可漏武装(漏了用户就要拨两次)。 */ +void Band_MarkRealStop(void) +{ + s_band_eater_armed = 1; +} + +/* 停后首个完整 Start 会被库吞掉(PlayTick 归 0 且不前进)。 + * 同一次调用里连发两次会被合成一次,仍然被吞;必须隔一拍再 Start。 + * 停后再起:先发本次 Start(可能被吞),再 20ms 后由 MainTask 补发一次。 */ +static uint8_t s_start_followup_pending; +static uint8_t s_start_followup_force; /* 1=补发中,禁止走「在播只换和弦」 */ +static rt_timer_t s_start_followup_timer; + +static uint8_t Band_TakeEaterArmed(void) +{ + uint8_t armed = s_band_eater_armed; + s_band_eater_armed = 0; + return armed; +} + +static void Band_StartFollowupTimerCb(void *parameter) +{ + (void)parameter; + MainTask_Sendmsg(MSG_ID_START_FOLLOWUP, 0, 0, 0); +} + +void Band_CancelStartFollowup(void) +{ + s_start_followup_pending = 0; + s_start_followup_force = 0; + if (s_start_followup_timer != RT_NULL) + rt_timer_stop(s_start_followup_timer); +} + +uint8_t Band_TakeStartFollowup(void) +{ + uint8_t pending = s_start_followup_pending; + s_start_followup_pending = 0; + return pending; +} + +void Band_BeginStartFollowup(void) +{ + s_start_followup_force = 1; +} + +void Band_EndStartFollowup(void) +{ + s_start_followup_force = 0; +} + +static void Band_PostStartFollowup(void) +{ + if (s_start_followup_pending) + return; + s_start_followup_pending = 1; + if (s_start_followup_timer == RT_NULL) + { + s_start_followup_timer = rt_timer_create("bandFu", + Band_StartFollowupTimerCb, + RT_NULL, + 500, /* 库吞掉停后首启;约 0.5s 后再 Start 才生效 */ + RT_TIMER_FLAG_ONE_SHOT); + } + if (s_start_followup_timer != RT_NULL) + { + rt_timer_stop(s_start_followup_timer); + rt_timer_start(s_start_followup_timer); + } + else + MainTask_Sendmsg(MSG_ID_START_FOLLOWUP, 0, 0, 0); +} + +static void Band_StartLocalRetry(uint8_t midi, uint8_t *notes, uint8_t n) +{ + Band_TickGate = 1; + AutoBandTop1_StartWithNote(midi); + AutoBandTop1_StartWithNotes(notes, n); +} + +static void Band_StartPieceRetry(uint8_t *notes, uint8_t n) +{ + Band_TickGate = 1; + AutoBandTop1_StartWithNotes(notes, n); +} + +static void Band_SyncStartRetry(void) +{ + Band_TickGate = 1; + AutoBandTop1_SyncStart(); +} + +void Band_AbsorbEatenStart(void) +{ + (void)0; +} + +/* 冷启动前按需 Stop:已由停止键停干净则跳过 */ +static void Band_StopBeforeColdStart(void) +{ + if (s_band_need_stop_before_start) + { + Band_MarkRealStop(); /* 若库仍在播,此 Stop 会武装吞噬器,由紧随的吸收处理 */ + AutoBandTop1_Stop(); + } + s_band_need_stop_before_start = 1; +} + +/* 诊断钩子:强制下次冷启动前先 Stop(RTT `band dirty`) */ +void Band_ForceStopBeforeStart(void) +{ + s_band_need_stop_before_start = 1; +} void Band_TrackPlayTick(void) { @@ -997,14 +1175,14 @@ static void Expert_StartWithChord(uint8_t chord_id, uint8_t *midi_note_buff, if (mGuiData[GUI_AUTOBAND_SW].Current) { /* 会话内勿 Stop:段落圆键切变奏后指板续奏若 Stop 会直接关音; - * 与节奏类型一致,仅换和弦/重进当前段。冷启动仍 Stop 清残留。 */ + * 与节奏类型一致,仅换和弦/重进当前段。 + * 冷启动:仅当库可能仍有残留时才 Stop;停止键后已停干净则跳过。 */ if (!session) - AutoBandTop1_Stop(); + Band_StopBeforeColdStart(); AutoBandTop1_SetLoopMode(0, 1); AutoBandTop1_SetRunVar(mGuiData[GUI_PLAY_SECTION].Current); midi = 0x3C + mGuiData[GUI_TRANSPOSE].Current; - AutoBandTop1_StartWithNote(midi); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + 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, @@ -1017,7 +1195,7 @@ static void Expert_StartWithChord(uint8_t chord_id, uint8_t *midi_note_buff, else { AutoBandTop1_SetPiecePlayMode(1, 0, 1920); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + 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", @@ -1037,7 +1215,7 @@ void TM1629_Handle(uint8_t key) if (mGuiData[GUI_TAB_INDEX].Current == 0) { if (key == 22) { Tap_to_Speed(); return; } - if (key == 23) { Stop_AutoBand(); return; } + if (key == 23) { Stop_AutoBand_Request(); return; } return; /* 忽略 0~21:不改 KEY_ID/USE_MIDI/PressFlag,不启停 OutTime_Stop */ } @@ -1066,7 +1244,7 @@ void TM1629_Handle(uint8_t key) } break; case 22:Tap_to_Speed(); break; - case 23:Stop_AutoBand(); break; + case 23:Stop_AutoBand_Request(); break; default: // 1 - 21 if (key >= 1 && key <= 21) { @@ -1229,20 +1407,19 @@ void Pick_Handle() StartFlag = 1; AutoBandTop1_SetLoopMode(0,1); midi = 0x3C + mGuiData[GUI_TRANSPOSE].Current; - AutoBandTop1_StartWithNote(midi); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + Band_StartLocalRetry(midi, midi_note_buff, note_cnt); } break; case 1: StartFlag = 1; AutoBandTop1_SetPiecePlayMode(1,0,1920); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + Band_StartPieceRetry(midi_note_buff, note_cnt); AutoBandTop1_SetLoopMode(0,1); break; case 0: /* 设置页内沿用万能:与主路径一致,始终 loop */ AutoBandTop1_SetPiecePlayMode(1, 0, 480); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + Band_StartPieceRetry(midi_note_buff, note_cnt); AutoBandTop1_SetLoopMode(0, 1); StartFlag = 1; break; @@ -1258,35 +1435,43 @@ void Pick_Handle() (unsigned)PressFlag, (unsigned)chord_id); break; } + { + uint8_t session_was_on = StartFlag ? 1u : 0u; StartFlag = 1; /* 普通:播放中指板 LED 全绿,关闭走向灯刷新 */ app_tm1629_all_on(LED_COLOR_G); if (mGuiData[GUI_AUTOBAND_SW].Current) { - AutoBandTop1_Stop(); + /* 播放中再拨:Stop+Start 重启段落;停止键后已停则勿二次 Stop。 + * 此 Stop 若真实停播会武装库的吞噬器 → 紧随吸收 */ + if (session_was_on) + { Band_MarkRealStop(); AutoBandTop1_Stop(); } + else + Band_StopBeforeColdStart(); AutoBandTop1_SetLoopMode(0,1); AutoBandTop1_SetRunVar(mGuiData[GUI_PLAY_SECTION].Current); midi = 0x3C + mGuiData[GUI_TRANSPOSE].Current; - AutoBandTop1_StartWithNote(midi); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + Band_StartLocalRetry(midi, midi_note_buff, note_cnt); LOG_I("pick", "local/normal root=%u name=%s", midi, AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?"); } else { AutoBandTop1_SetPiecePlayMode(1, 0, 1920); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + Band_StartPieceRetry(midi_note_buff, note_cnt); AutoBandTop1_SetLoopMode(0, 1); LOG_I("pick", "normal/rhythm piece name=%s notes=%u chord=%u", AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", (unsigned)note_cnt, (unsigned)chord_id); } + } break; case 1: /* 专业:播放中再拨片不 Stop+Start(库会把未消费的 Start 标志清掉 → 静音); * 本地曲播放中仅换和弦音续播,节奏路径 piece 重启本就安全。 */ - if (Band_IsPlaying() && mGuiData[GUI_AUTOBAND_SW].Current) + if (!s_start_followup_force && + Band_IsPlaying() && mGuiData[GUI_AUTOBAND_SW].Current) { Accomp_UpdatePlayingChord(); LOG_I("pick", "expert/pick skip restart playing chord=%u", (unsigned)chord_id); @@ -1299,7 +1484,7 @@ void Pick_Handle() StartFlag = 1; s_outro_led_all_green = 0; /* 拨片重开 piece,恢复走向灯 */ AutoBandTop1_SetPiecePlayMode(1, 0, 480); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + Band_StartPieceRetry(midi_note_buff, note_cnt); AutoBandTop1_SetLoopMode(0, 1); LOG_I("pick", "universal piece name=%s press=%u notes=%u chord=%u", AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", diff --git a/Global/Global.h b/Global/Global.h index 1b4e9ae..b8b1414 100644 --- a/Global/Global.h +++ b/Global/Global.h @@ -32,6 +32,8 @@ #define MSG_ID_BLE_UI_REFRESH 28 #define MSG_ID_BLE_STYLE_SET 29 #define MSG_ID_LOW_BAT_TIP 30 +#define MSG_ID_STOP_BAND 31 /* 停止键/收束:MainTask 串行 Stop,避免与拨片起奏竞态 */ +#define MSG_ID_START_FOLLOWUP 32 /* 停后首启被库吞:下一拍补发真正起奏 */ #define GPIO_PIN_RESET false #define GPIO_PIN_SET true @@ -210,7 +212,6 @@ extern GUI_SWITCH ParamGuiData[]; extern void (*CurrUIProcress)(DisplayTaskMessage_Type xEvent); -void Stop_AutoBand(); /* 普通模式:松手后偶拍收束轮询(由 Read_Cur_Measure 调用) */ void FreeMode_PollEvenBeatStop(void); /* 当前调一级大和弦(把位1 + maj);开机/进模式/前奏/拨片兜底共用 */ @@ -238,6 +239,17 @@ void TM1617_Handle(uint8_t key); void TM1629_Handle(uint8_t key); void Pick_Handle(); void Loop_Light_Handle(uint8_t measure); +void Stop_AutoBand(void); +void Stop_AutoBand_Soft(void); +extern uint8_t Band_TickGate; +void Stop_AutoBand_Request(void); +uint8_t Band_IsPlaying(void); +void Band_ForceStopBeforeStart(void); +void Band_AbsorbEatenStart(void); +void Band_MarkRealStop(void); +uint8_t Band_TakeStartFollowup(void); +void Band_BeginStartFollowup(void); +void Band_EndStartFollowup(void); void BL_Set_led(uint8_t Led, uint8_t chord_type); diff --git a/project/src/wk_system.c b/project/src/wk_system.c index d71d446..721269f 100644 --- a/project/src/wk_system.c +++ b/project/src/wk_system.c @@ -132,7 +132,8 @@ volatile uint32_t tmr6_ticks; */ __WEAK void wk_tmr6_1ms_tick(void) { - AutoBandTop1_Metronome_External_1msTick(); + if (Band_TickGate) + AutoBandTop1_Metronome_External_1msTick(); } /** diff --git a/task/task_init.c b/task/task_init.c index b1a8d91..0969858 100644 --- a/task/task_init.c +++ b/task/task_init.c @@ -110,6 +110,24 @@ void TaskMainThread_entry(void* parameter) if (!System_LowBat_IsActive()) Pick_Handle(); break; + case MSG_ID_STOP_BAND: + if (!System_LowBat_IsActive()) + { + if (xEvent.ID) + Stop_AutoBand_Soft(); + else + Stop_AutoBand(); + } + break; + case MSG_ID_START_FOLLOWUP: + /* 停后补发:强制再 Start,勿因 NotifyPlayActivity 误判「在播」只换和弦 */ + if (!System_LowBat_IsActive() && Band_TakeStartFollowup()) + { + Band_BeginStartFollowup(); + Pick_Handle(); + Band_EndStartFollowup(); + } + break; case MSG_ID_LOOP_LIGHT: if (!System_LowBat_IsActive()) Loop_Light_Handle(xEvent.ID);