From 5f41af27e713e861c3c7770e60c28c41b436a0cb Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Tue, 15 Sep 2026 19:13:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(expert):=20=E4=B8=93=E4=B8=9A=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=BB=85=E6=8C=89=E6=8C=87=E6=9D=BF=E5=8D=B3=E5=8F=AF?= =?UTF-8?q?=E7=BB=AD=E5=A5=8F=E4=B8=8B=E4=B8=80=E5=92=8C=E5=BC=A6=EF=BC=8C?= =?UTF-8?q?=E6=97=A0=E9=9C=80=E5=86=8D=E6=8B=A8=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App 连机专业模式下,起奏后按亮灯指板即可继续播放: - 本地曲目(App 和弦曲目)/段落停止时:指板键投递 MSG_ID_BP_KEY 给主任务, 走与「指板+拨片」完全相同的 Pick_Handle 起奏路径(扫描线程内直接 Stop+Start 会无声,主任务上下文正常),Pick_Handle 内 Return_Light_buff 通知 App 亮下一节 - 节奏库播放中:保持说明书行为,仅 Note_On 换和弦音 + Return_Light_buff - 去掉 Last_Tm1926D_Key 去重:相邻两节可能是同一和弦键,松手再按必须能续奏 - 新增 Band_TrackPlayTick/Band_IsPlaying:8ms 周期跟踪 AutoBand PlayTick 判定伴奏是否进行中 - 抽出 Expert_StartWithChord 公共起奏函数,拨片/指板共用 - tools/_test_expert_fret_restart.py: 指板续奏 RTT 回归测试 HIL 验证: pitch reg OK=41 FAIL=0; packs 9/9 PASS Co-authored-by: Cursor --- Global/Global.c | 131 +++++++++++++++++++++-------- tools/_test_expert_fret_restart.py | 66 +++++++++++++++ 2 files changed, 162 insertions(+), 35 deletions(-) create mode 100644 tools/_test_expert_fret_restart.py diff --git a/Global/Global.c b/Global/Global.c index c18eade..605b86a 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -106,6 +106,9 @@ const uint8_t Version[3] = {0,2,12}; /* major.minor.patch — 0.2.12: autoclose uint8_t Led = 8; bool PressFlag = 0; +void Return_Light_buff(uint8_t key_id_1629); +uint8_t Band_IsPlaying(void); + uint8_t AutoCloseTime = 15; uint16_t mAutoPowerOffCount = 15 * 60; bool AutoCloseFlag = true; @@ -774,11 +777,77 @@ uint8_t BP_Transpose(uint8_t key_idx) } +/* 伴奏活动跟踪:AutoBand PlayTick 前进即视为播放中。 + * 供专业模式指板键区分「播放中换和弦」与「段落结束后续奏重启」。 */ +static uint64_t s_play_tick_last = 0; +static uint32_t s_play_adv_tick = 0; /* 最近一次 PlayTick 前进的系统 tick */ + +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(); + } +} + +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; + + StartFlag = 1; + if (mGuiData[GUI_AUTOBAND_SW].Current) + { + AutoBandTop1_Stop(); + 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); + 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); + AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); + AutoBandTop1_SetLoopMode(0, 1); + 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; - static uint8_t Last_Tm1926D_Key = 0; /* 万能:指板不改和弦、不启停伴奏;保留拍速/停止 */ if (mGuiData[GUI_TAB_INDEX].Current == 0) @@ -857,18 +926,30 @@ void TM1629_Handle(uint8_t key) 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 != KEY_ID_1629) + if(mGuiData[GUI_TAB_INDEX].Current == 1 && StartFlag) { - uint8_t midi_note_buff[4] = {0,}; - uint8_t note_cnt = 0; - Last_Tm1926D_Key = KEY_ID_1629; - //AutoBandTop1_SetPiecePlayMode(1,0,480); - //AutoBandTop1_StartWithNotes(midi_note_buff,GetChordNotesByType(KEY_ID_1629,chord_type_index_map[KEY_ID_1629].type,midi_note_buff)); - //AutoBandTop1_SetLoopMode(0,1); - 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++) + /* 不按 Last_Tm1926D_Key 去重:App 曲目相邻两节可能是同一和弦键, + * 松手再按同一键也必须能续奏(与「指板+拨片」一致,拨片本无去重) */ + if (mGuiData[GUI_AUTOBAND_SW].Current || !Band_IsPlaying()) { - AutoBandTop1_Note_On(midi_note_buff[i],0x50); + /* 本地曲目(App 和弦曲目):段落结束/循环点处仅按指板即续奏下一节。 + * 转主任务走 Pick_Handle,与「指板+拨片」完全等价—— + * 扫描线程内直接 Stop+Start 重启会无声,主任务上下文则正常。 + * Pick_Handle 内会调 Return_Light_buff 通知 App 亮下一节。 */ + 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); } } } @@ -1002,30 +1083,8 @@ void Pick_Handle() break; case 1: - StartFlag = 1; - if (mGuiData[GUI_AUTOBAND_SW].Current) - { - /* 本地曲目:与普通模式同一套起奏(0x3C 根音 + 和弦音) */ - AutoBandTop1_Stop(); - 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); - LOG_I("pick", "local/expert root=%u name=%s sw=%u tab=%u", - midi, - AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", - (unsigned)mGuiData[GUI_AUTOBAND_SW].Current, - (unsigned)mGuiData[GUI_TAB_INDEX].Current); - } - else - { - AutoBandTop1_SetPiecePlayMode(1,0,1920); - AutoBandTop1_StartWithNotes(midi_note_buff, note_cnt); - AutoBandTop1_SetLoopMode(0,1); - LOG_I("pick", "expert/rhythm piece name=%s", - AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?"); - } + /* 首次拨片起奏与后续指板续奏共用同一专业起奏路径 */ + Expert_StartWithChord(chord_id, midi_note_buff, note_cnt, "pick"); break; case 0: /* 万能:和弦走向用 piece 模式;须 SetLoopMode,否则无循环时常听不到伴奏 */ @@ -1509,6 +1568,8 @@ void Read_Cur_Measure() (tab == 0 || tab == 2) || (tab == 3 && (last == 0 || last == 2)); + Band_TrackPlayTick(); + if (loop_light_ok && StartFlag) { if(now - last_tick_i >= 10) diff --git a/tools/_test_expert_fret_restart.py b/tools/_test_expert_fret_restart.py new file mode 100644 index 0000000..4bac862 --- /dev/null +++ b/tools/_test_expert_fret_restart.py @@ -0,0 +1,66 @@ +"""One-off: expert rhythm (1.bin) piece mode ends after the piece window; +press fretboard only (no pick) after it stops -> must restart (trigger=fret + PITCH).""" +import os +import sys +import time + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import rtt_pitch_reg_test as R + + +def main() -> int: + R.import_deps() + jlink = R.connect_jlink("AT32F403AC") # same default as rtt_pitch_reg_test + try: + cb = R.find_rtt_control_block(jlink) + if cb is None: + print("FAIL: RTT CB not found") + return 1 + print(f"RTT CB @ 0x{cb:08X}", flush=True) + jlink.rtt_start(cb) + R.wait_rtt_ready(jlink) + sess = R.RttSession(jlink) + sess.pump(0.3) + + sess.cmd("log clear", 0.3) + sess.cmd("tone bin1 0", 1.2) # expert + rhythm 1.bin (piece 0..1920ms) + sess.cmd("tone start", 0.8) # pick start, piece plays then stops + print(">> playing window (piece ~2s) ...", flush=True) + sess.pump(4.0) # wait until piece ended, PlayTick frozen + + base = len(sess.lines) + sess.cmd("chord key 0", 0.4) + sess.cmd("chord key 8", 0.2) # fretboard only, no pick + sess.pump(3.0) # capture restart + + new = sess.lines[base:] + joined = "\n".join(new) + trig = [l for l in new if "trigger=fret" in l] + change = [l for l in new if "expert/fret change" in l] + pitch = [l for l in new if "[PITCH] on" in l] + print("---- full session ----", flush=True) + for l in sess.lines: + print(l, flush=True) + print("---- captured after fret-only press ----", flush=True) + for l in new: + print(l, flush=True) + print("---- verdict ----", flush=True) + handled = trig or change + if handled and pitch: + path = "restart" if trig else "note_on-change" + print(f"PASS fret-only continue: path={path}, PITCH on x{len(pitch)}") + return 0 + print("FAIL: no handler log or no pitch") + return 1 + finally: + try: + sess.cmd("chord key 23", 0.3) # stop band, leave device quiet + sess.pump(0.3) + except Exception: + pass + jlink.close() + + +if __name__ == "__main__": + raise SystemExit(main()) +