From 605af8f0458b1703111d2947f14a9265ccb729d5 Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Thu, 15 Oct 2026 14:38:23 +0800 Subject: [PATCH] fix(chord): apply fixed-key voicings from the new chord sheet Stop adding transpose to every string and AutoBand tone. Each degree now uses the C-major source fret from the sheet, with +1 only where the sheet marks it. Co-authored-by: Cursor --- Global/Global.c | 174 +++++++++++++++++++------------- Global/Global.h | 1 + UI/UI_SongMode.c | 40 +++++--- midi/midi_tables.c | 26 ++--- tools/test_fixed_key_voicing.py | 90 +++++++++++++++++ tools/test_tone_voicing.py | 12 +-- 6 files changed, 244 insertions(+), 99 deletions(-) create mode 100644 tools/test_fixed_key_voicing.py diff --git a/Global/Global.c b/Global/Global.c index 7dfe23f..ea1d536 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -30,6 +30,64 @@ GUI_SWITCH ParamGuiData[] = { 0, 0, 2, ALL_MODE_PARAM}, // 2 }; +/* ======== 固定调(自动伴奏新更改和弦规则.xlsx「和弦固定调逻辑」) ======== + * 目标调各级不再整体加 GUI_TRANSPOSE,而是取 C 调「源品位」同一行; + * 表头标 +1 的级整行再升半音(发音位置不变)。升号调(C#/Eb/F#/G#/A#) + * 用低半音自然调的映射再全体 +1。 */ +static const int8_t FIXED_KEY_SRC[7][7] = /* [C D E F G A B][级 I..VII] → C 调源品位 0~6 */ +{ + {0, 1, 2, 3, 4, 5, 6}, /* C */ + {1, 2, 3, 4, 5, 6, 0}, /* D */ + {2, 3, 4, 5, 6, 0, 1}, /* E */ + {3, 4, 5, 5, 0, 1, 2}, /* F */ + {4, 5, 6, 0, 1, 2, 3}, /* G */ + {5, 6, 0, 1, 2, 3, 4}, /* A */ + {6, 0, 1, 2, 3, 4, 5}, /* B */ +}; +static const uint8_t FIXED_KEY_SHARP[7][7] = /* 该行相对源品位再 +1(表头「源 品 X +1」) */ +{ + {0, 0, 0, 0, 0, 0, 0}, /* C */ + {0, 0, 1, 0, 0, 0, 1}, /* D */ + {0, 1, 1, 0, 0, 1, 1}, /* E */ + {0, 0, 0, 1, 0, 0, 0}, /* F */ + {0, 0, 0, 0, 0, 0, 1}, /* G */ + {0, 0, 1, 0, 0, 1, 1}, /* A */ + {0, 1, 1, 0, 1, 1, 1}, /* B */ +}; +static const uint8_t FIXED_KEY_ROW[12] = {0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6}; /* 移调 → 自然调行 */ +static const uint8_t FIXED_KEY_HALF[12] = {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0}; /* 升号调:低半音映射 +1 */ + +/* 返回 C 调源品位 0~6;*add = 需叠加半音(调内 +1 行 + 升号调 +1,取值 0~2) */ +static uint8_t FixedKey_SourceFret(uint8_t group, uint8_t *add) +{ + uint8_t t = (uint8_t)mGuiData[GUI_TRANSPOSE].Current % 12u; + uint8_t row = FIXED_KEY_ROW[t]; + *add = (uint8_t)(FIXED_KEY_SHARP[row][group] + FIXED_KEY_HALF[t]); + return (uint8_t)FIXED_KEY_SRC[row][group]; +} + +/* 固定调填 6 弦 USE_MIDI:源品位行 + add;指板单独升号(PitchOffset==2)再 +1 */ +void FretChord_FillUseMidi(uint8_t key) +{ + uint8_t group, type, add = 0, src; + const STRING_MIDI *ORGAN_MIDI; + + if (key < 1u || key > 21u) + return; + group = (uint8_t)((key - 1u) / 3u); + src = FixedKey_SourceFret(group, &add); + if (chord_type_index_map[key].PitchOffset == 2u) + add = (uint8_t)(add + 1u); + ORGAN_MIDI = OrganChord_Midi_Table[src]; + type = chord_type_index_map[key].type; + USE_MIDI.Midi_1 = (uint8_t)(ORGAN_MIDI[type].Midi_1 + add); + USE_MIDI.Midi_2 = (uint8_t)(ORGAN_MIDI[type].Midi_2 + add); + USE_MIDI.Midi_3 = (uint8_t)(ORGAN_MIDI[type].Midi_3 + add); + USE_MIDI.Midi_4 = (uint8_t)(ORGAN_MIDI[type].Midi_4 + add); + USE_MIDI.Midi_5 = (uint8_t)(ORGAN_MIDI[type].Midi_5 + add); + USE_MIDI.Midi_6 = (uint8_t)(ORGAN_MIDI[type].Midi_6 + add); +} + const LEBEL_UI UI_Label[] = { @@ -854,7 +912,7 @@ void MasterVolume_Reapply(void) Send_volume(vol); } -/* 当前调一级大和弦:把位1 + maj + 自然音(再经 GUI_TRANSPOSE 移调 → C调即 do mi sol) */ +/* 当前调一级大和弦:把位1 + maj + 自然音(固定调映射 → C调即 do mi sol) */ void Accomp_ApplyDefaultIChord(void) { KEY_ID_1629 = 1; @@ -1627,9 +1685,6 @@ static void Expert_StartWithChord(uint8_t chord_id, uint8_t *midi_note_buff, void TM1629_Handle(uint8_t key) { - STRING_MIDI *MIDI; - const STRING_MIDI *ORGAN_MIDI; - /* 指板/拍速/停止键均为用户操作 */ ResetAutoPowerCount(); @@ -1684,7 +1739,6 @@ void TM1629_Handle(uint8_t key) FreeMode_ResumePieceLoop(); PressFlag = 1; } - uint8_t group = (key - 1) / 3; /* 1~3→0, 4~6→1, ..., 19~21→6 */ KEY_ID_1629 = key; /* 专业/普通(及对应设置页):记下最后指板和弦;专业同时记移调 */ { @@ -1698,35 +1752,8 @@ void TM1629_Handle(uint8_t 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; + /* 固定调:按当前移调取 C 调源品位六弦,不再整体加 GUI_TRANSPOSE */ + FretChord_FillUseMidi(key); if(mGuiData[GUI_TAB_INDEX].Current == 1 && StartFlag) { @@ -2118,11 +2145,20 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) uint8_t n = 0; uint8_t i; uint8_t root; + uint8_t add = 0; + uint8_t src; buff[0] = buff[1] = buff[2] = buff[3] = 0; - switch(MIDI_Index) + if (MIDI_Index < 1 || MIDI_Index > 21) + return 0; + + /* 固定调:按当前移调把该级映射到 C 调源品位(发音位置不变,音高随表), + * 不再对结果整体加 GUI_TRANSPOSE。各 case 数值 = 新表 C 大调「和弦音高」列。 */ + src = FixedKey_SourceFret((uint8_t)((MIDI_Index - 1) / 3), &add); + + switch(src) { - case 1:case 2:case 3: + case 0: /* I:C 品位 */ switch(type) { case 0: @@ -2154,7 +2190,7 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) break; } break; - case 4:case 5:case 6: + case 1: /* II:D 品位 */ switch(type) { case 0: @@ -2186,71 +2222,71 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) break; } break; - case 7:case 8:case 9: + case 2: /* III:E 品位(整组低八度;M/m 三和弦带 E4) */ switch(type) { case 0: - buff[0] = 52;buff[1] = 56;buff[2] = 59; + buff[0] = 40;buff[1] = 44;buff[2] = 47;buff[3] = 64; break; case 1: - buff[0] = 52;buff[1] = 55;buff[2] = 59; + buff[0] = 40;buff[1] = 43;buff[2] = 47;buff[3] = 64; break; case 2: - buff[0] = 52;buff[1] = 55;buff[2] = 59;buff[3] = 62; + buff[0] = 40;buff[1] = 43;buff[2] = 47;buff[3] = 50; break; case 3: - buff[0] = 52;buff[1] = 56;buff[2] = 59;buff[3] = 62; + buff[0] = 40;buff[1] = 44;buff[2] = 47;buff[3] = 50; break; case 4: - buff[0] = 52;buff[1] = 56;buff[2] = 59;buff[3] = 63; + buff[0] = 40;buff[1] = 44;buff[2] = 47;buff[3] = 51; break; case 5: - buff[0] = 52;buff[1] = 56;buff[2] = 59;buff[3] = 66; + buff[0] = 40;buff[1] = 44;buff[2] = 47;buff[3] = 54; break; case 6: - buff[0] = 52;buff[1] = 55;buff[2] = 58;buff[3] = 62; + buff[0] = 40;buff[1] = 43;buff[2] = 46;buff[3] = 50; break; case 7: - buff[0] = 52;buff[1] = 54;buff[2] = 59; + buff[0] = 40;buff[1] = 42;buff[2] = 47; break; case 8: - buff[0] = 52;buff[1] = 57;buff[2] = 59; + buff[0] = 40;buff[1] = 45;buff[2] = 47; break; } break; - case 10:case 11:case 12: + case 3: /* IV:F 品位(整组低八度) */ switch(type) { case 0: - buff[0] = 53;buff[1] = 57;buff[2] = 60; + buff[0] = 41;buff[1] = 45;buff[2] = 48; break; case 1: - buff[0] = 53;buff[1] = 56;buff[2] = 60; + buff[0] = 41;buff[1] = 44;buff[2] = 48; break; case 2: - buff[0] = 53;buff[1] = 56;buff[2] = 60;buff[3] = 63; + buff[0] = 41;buff[1] = 44;buff[2] = 48;buff[3] = 51; break; case 3: - buff[0] = 53;buff[1] = 57;buff[2] = 60;buff[3] = 63; + buff[0] = 41;buff[1] = 45;buff[2] = 48;buff[3] = 51; break; case 4: - buff[0] = 53;buff[1] = 57;buff[2] = 60;buff[3] = 64; + buff[0] = 41;buff[1] = 45;buff[2] = 48;buff[3] = 52; break; case 5: - buff[0] = 53;buff[1] = 57;buff[2] = 60;buff[3] = 67; + buff[0] = 41;buff[1] = 45;buff[2] = 48;buff[3] = 55; break; case 6: - buff[0] = 53;buff[1] = 56;buff[2] = 59;buff[3] = 63; + buff[0] = 41;buff[1] = 44;buff[2] = 47;buff[3] = 51; break; case 7: - buff[0] = 53;buff[1] = 55;buff[2] = 60; + buff[0] = 41;buff[1] = 43;buff[2] = 48; break; case 8: - buff[0] = 53;buff[1] = 58;buff[2] = 60; + buff[0] = 41;buff[1] = 46;buff[2] = 48; break; } break; - case 13:case 14:case 15: + case 4: /* V:G 品位 */ switch(type) { case 0: @@ -2282,7 +2318,7 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) break; } break; - case 16:case 17:case 18: + case 5: /* VI:A 品位 */ switch(type) { case 0: @@ -2314,30 +2350,30 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) break; } break; - case 19:case 20:case 21: + case 6: /* VII:B 品位(Bm/B7/BM7/Bad9/Bm7-5 按新表改) */ 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; + buff[0] = 47;buff[1] = 50;buff[2] = 53; 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: + case 4: buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 58; break; - case 6: + case 5: buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 60; break; + case 6: + buff[0] = 47;buff[1] = 50;buff[2] = 53;buff[3] = 57; + break; case 7: buff[0] = 47;buff[1] = 49;buff[2] = 54; break; @@ -2354,6 +2390,10 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) } n = (buff[3] != 0) ? 4 : 3; + + /* 固定调:调内 +1 行 / 升号调全体 +1(替代旧的整体加 GUI_TRANSPOSE) */ + for (i = 0; i < n; i++) + buff[i] += add; switch (chord_type_index_map[MIDI_Index].PitchOffset) { case 2: /* # 升:整体 +1 半音 */ @@ -2363,8 +2403,6 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff) default: break; } - for (i = 0; i < n; i++) - buff[i] += mGuiData[GUI_TRANSPOSE].Current; /* 音师:仅所有调的 #四级(键 10~12 + 升)— 整组低八度,音域压在一个八度内 * 最低音 = #IV,最高音 ≤ 自然 IV。勿对六级等其它升号和弦套用。 */ diff --git a/Global/Global.h b/Global/Global.h index 8f28316..5dda84a 100644 --- a/Global/Global.h +++ b/Global/Global.h @@ -276,6 +276,7 @@ void AppNeckLed_ClearGuide(void); uint8_t AppNeckLed_IsGuide(void); uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff); +void FretChord_FillUseMidi(uint8_t key); /* 伴奏已在播时仅更新和弦音,不 Restart(快转调) */ void Accomp_UpdatePlayingChord(void); /* 切模式/停键前记住专业移调与最后指板和弦 */ diff --git a/UI/UI_SongMode.c b/UI/UI_SongMode.c index e7618b8..b8207d3 100644 --- a/UI/UI_SongMode.c +++ b/UI/UI_SongMode.c @@ -111,12 +111,20 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) int8_t new_trans = mGuiData[GUI_TRANSPOSE].Current; delta = new_trans - last_value; - USE_MIDI.Midi_1 += delta; - USE_MIDI.Midi_2 += delta; - USE_MIDI.Midi_3 += delta; - USE_MIDI.Midi_4 += delta; - USE_MIDI.Midi_5 += delta; - USE_MIDI.Midi_6 += delta; + /* 固定调:换调后按新映射重填 6 弦;未按指板时维持旧平移 */ + if (KEY_ID_1629 >= 1 && KEY_ID_1629 <= 21) + { + FretChord_FillUseMidi(KEY_ID_1629); + } + else + { + USE_MIDI.Midi_1 += delta; + USE_MIDI.Midi_2 += delta; + USE_MIDI.Midi_3 += delta; + USE_MIDI.Midi_4 += delta; + USE_MIDI.Midi_5 += delta; + USE_MIDI.Midi_6 += delta; + } if(StartFlag) { @@ -416,12 +424,20 @@ void UI_ApplyModeKeysOnSettings(DisplayTaskMessage_Type msg) USART4_SendData(transpose, 5); new_trans = (int8_t)mGuiData[GUI_TRANSPOSE].Current; delta = (int8_t)(new_trans - (int8_t)lastvalue); - USE_MIDI.Midi_1 += delta; - USE_MIDI.Midi_2 += delta; - USE_MIDI.Midi_3 += delta; - USE_MIDI.Midi_4 += delta; - USE_MIDI.Midi_5 += delta; - USE_MIDI.Midi_6 += delta; + /* 固定调:换调后按新映射重填 6 弦;未按指板时维持旧平移 */ + if (KEY_ID_1629 >= 1 && KEY_ID_1629 <= 21) + { + FretChord_FillUseMidi(KEY_ID_1629); + } + else + { + USE_MIDI.Midi_1 += delta; + USE_MIDI.Midi_2 += delta; + USE_MIDI.Midi_3 += delta; + USE_MIDI.Midi_4 += delta; + USE_MIDI.Midi_5 += delta; + USE_MIDI.Midi_6 += delta; + } if (StartFlag && KEY_ID_1629 != 22 && KEY_ID_1629 != 23) Accomp_UpdatePlayingChord(); } diff --git a/midi/midi_tables.c b/midi/midi_tables.c index ab65cdc..d6e188d 100644 --- a/midi/midi_tables.c +++ b/midi/midi_tables.c @@ -24,9 +24,9 @@ CHORD_TYPE_INDEX chord_type_index_map[22] = {1, 0, 1, 0}, // index 16 {1, 0, 1, 0}, // index 17 {1, 0, 1, 0}, // index 18 - {0, 0, 1, 0}, // index 19 - {0, 0, 1, 0}, // index 20 - {0, 0, 1, 0}, // index 21 + {1, 0, 1, 0}, // index 19 (VII 出厂小三,对照新表 Bm) + {1, 0, 1, 0}, // index 20 + {1, 0, 1, 0}, // index 21 }; STRING_MIDI MIDI_1[] = @@ -58,7 +58,7 @@ STRING_MIDI MIDI_2[] = STRING_MIDI MIDI_3[] = { {0x28,0x2F,0x34,0x38,0x3B,0x40,"E"}, - {0x28,0x37,0x3B,0x40,0x3B,0x40,"Em"}, + {0x28,0x2F,0x34,0x37,0x3B,0x40,"Em"}, {0x28,0x2F,0x34,0x37,0x3B,0x3E,"Em7"}, {0x28,0x2F,0x34,0x38,0x3B,0x3E,"E7"}, {0x28,0x2F,0x34,0x38,0x3B,0x3F,"EM7"}, @@ -70,7 +70,7 @@ STRING_MIDI MIDI_3[] = STRING_MIDI MIDI_4[] = { - {0x29,0x39,0x3C,0x41,0x3C,0x41,"F"}, + {0x29,0x30,0x35,0x39,0x3C,0x41,"F"}, {0x29,0x30,0x35,0x38,0x3C,0x41,"Fm"}, {0x29,0x30,0x35,0x38,0x3C,0x3F,"Fm7"}, {0x29,0x30,0x35,0x39,0x3C,0x3F,"F7"}, @@ -83,7 +83,7 @@ STRING_MIDI MIDI_4[] = STRING_MIDI MIDI_5[] = { - {0x2B,0x2F,0x37,0x3B,0x43,0x43,"G"}, + {0x2B,0x2F,0x32,0x37,0x3B,0x43,"G"}, {0x2B,0x2E,0x32,0x37,0x3A,0x43,"Gm"}, {0x2B,0x2E,0x32,0x37,0x3A,0x41,"Gm7"}, {0x2B,0x2F,0x32,0x37,0x3B,0x41,"G7"}, @@ -97,7 +97,7 @@ STRING_MIDI MIDI_5[] = STRING_MIDI MIDI_6[] = { {0x28,0x2D,0x34,0x39,0x3D,0x40,"A"}, - {0x28,0x2D,0x39,0x3C,0x40,0x40,"Am"}, + {0x28,0x2D,0x34,0x39,0x3C,0x40,"Am"}, {0x28,0x2D,0x34,0x37,0x3C,0x40,"Am7"}, {0x28,0x2D,0x34,0x37,0x3D,0x40,"A7"}, {0x28,0x2D,0x34,0x38,0x3D,0x40,"AM7"}, @@ -118,7 +118,7 @@ STRING_MIDI MIDI_7[] = {0x29,0x2F,0x32,0x35,0x39,0x3E,"Bm7-5"}, {0x29,0x2F,0x31,0x36,0x3B,0x3D,"Bsus2"}, {0x29,0x2F,0x34,0x36,0x3B,0x40,"Bsus4"}, - {0x29,0x2F,0x32,0x35,0x3B,0x3E,"Bdim"}, + {0x29,0x2F,0x32,0x35,0x3B,0x3E,"Bm-5"}, }; const STRING_MIDI OrganMIDI_1[] = @@ -150,7 +150,7 @@ const STRING_MIDI OrganMIDI_2[] = const STRING_MIDI OrganMIDI_3[] = { {0x28,0x2F,0x34,0x38,0x3B,0x40,"E"}, - {0x28,0x37,0x3B,0x40,0x3B,0x40,"Em"}, + {0x28,0x2F,0x34,0x37,0x3B,0x40,"Em"}, {0x28,0x2F,0x34,0x37,0x3B,0x3E,"Em7"}, {0x28,0x2F,0x34,0x38,0x3B,0x3E,"E7"}, {0x28,0x2F,0x34,0x38,0x3B,0x3F,"EM7"}, @@ -162,7 +162,7 @@ const STRING_MIDI OrganMIDI_3[] = const STRING_MIDI OrganMIDI_4[] = { - {0x29,0x39,0x3C,0x41,0x3C,0x41,"F"}, + {0x29,0x30,0x35,0x39,0x3C,0x41,"F"}, {0x29,0x30,0x35,0x38,0x3C,0x41,"Fm"}, {0x29,0x30,0x35,0x38,0x3C,0x3F,"Fm7"}, {0x29,0x30,0x35,0x39,0x3C,0x3F,"F7"}, @@ -175,7 +175,7 @@ const STRING_MIDI OrganMIDI_4[] = const STRING_MIDI OrganMIDI_5[] = { - {0x2B,0x2F,0x37,0x3B,0x43,0x43,"G"}, + {0x2B,0x2F,0x32,0x37,0x3B,0x43,"G"}, {0x2B,0x2E,0x32,0x37,0x3A,0x43,"Gm"}, {0x2B,0x2E,0x32,0x37,0x3A,0x41,"Gm7"}, {0x2B,0x2F,0x32,0x37,0x3B,0x41,"G7"}, @@ -189,7 +189,7 @@ const STRING_MIDI OrganMIDI_5[] = const STRING_MIDI OrganMIDI_6[] = { {0x28,0x2D,0x34,0x39,0x3D,0x40,"A"}, - {0x28,0x2D,0x39,0x3C,0x40,0x40,"Am"}, + {0x28,0x2D,0x34,0x39,0x3C,0x40,"Am"}, {0x28,0x2D,0x34,0x37,0x3C,0x40,"Am7"}, {0x28,0x2D,0x34,0x37,0x3D,0x40,"A7"}, {0x28,0x2D,0x34,0x38,0x3D,0x40,"AM7"}, @@ -210,7 +210,7 @@ const STRING_MIDI OrganMIDI_7[] = {0x29,0x2F,0x32,0x35,0x39,0x3E,"Bm7-5"}, {0x29,0x2F,0x31,0x36,0x3B,0x3D,"Bsus2"}, {0x29,0x2F,0x34,0x36,0x3B,0x40,"Bsus4"}, - {0x29,0x2F,0x32,0x35,0x3B,0x3E,"Bdim"}, + {0x29,0x2F,0x32,0x35,0x3B,0x3E,"Bm-5"}, }; /* 和弦根音表索引 */ diff --git a/tools/test_fixed_key_voicing.py b/tools/test_fixed_key_voicing.py new file mode 100644 index 0000000..af65739 --- /dev/null +++ b/tools/test_fixed_key_voicing.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +"""固定调和弦六弦音高:RTT STRING_OK 对照《自动伴奏新更改和弦规则.xlsx》。 + +普通模式按下指板键后读 USE_MIDI(六弦→一弦)。先 chord xpose 再 chord key, +因为换调命令本身不重填六弦。 + + python tools/test_fixed_key_voicing.py +""" +from __future__ import annotations + +import os +import re +import sys +import time + +TOOLS = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, TOOLS) + +from k1_harness.transports.rtt_session import open_session # noqa: E402 + +NOTE_RE = re.compile(r"STRING_OK s=(\d+) note=(\d+)") + +# (调名, GUI_TRANSPOSE, 键, 和弦名, 六弦→一弦 MIDI) +# 默认品质:I/IV/V 大三,II/III/VI/VII 小三。数值来自各调 sheet 对应行。 +CASES = [ + ("C", 0, 1, "C", (43, 48, 52, 55, 60, 64)), + ("C", 0, 4, "Dm", (41, 45, 50, 57, 62, 65)), + ("C", 0, 7, "Em", (40, 47, 52, 55, 59, 64)), + ("C", 0, 10, "F", (41, 48, 53, 57, 60, 65)), + ("C", 0, 13, "G", (43, 47, 50, 55, 59, 67)), + ("C", 0, 16, "Am", (40, 45, 52, 57, 60, 64)), + ("C", 0, 19, "Bm", (42, 47, 50, 54, 59, 62)), + # G:源品位 V/VI/VII/I/II/III/IV+1 + ("G", 7, 1, "G", (43, 47, 50, 55, 59, 67)), + ("G", 7, 4, "Am", (40, 45, 52, 57, 60, 64)), + ("G", 7, 7, "Bm", (42, 47, 50, 54, 59, 62)), + ("G", 7, 10, "C", (43, 48, 52, 55, 60, 64)), + ("G", 7, 13, "D", (42, 45, 50, 57, 62, 66)), + ("G", 7, 16, "Em", (40, 47, 52, 55, 59, 64)), + ("G", 7, 19, "#Fm", (42, 49, 54, 58, 61, 66)), + # D I = C 调 II 行;F# I = F 调 I 行全体 +1 + ("D", 2, 1, "D", (42, 45, 50, 57, 62, 66)), + ("F#", 6, 1, "F#", (42, 49, 54, 58, 61, 66)), +] + + +def last_string_note(text: str, string_n: int) -> int | None: + found = None + for m in NOTE_RE.finditer(text or ""): + if int(m.group(1)) == string_n: + found = int(m.group(2)) + return found + + +def main() -> int: + fails = 0 + print("=== 固定调六弦音高 RTT ===", flush=True) + with open_session(reset=False) as sess: + sess.cmd("tone pick f", settle=1.0) + sess.cmd("chord key 23", settle=0.3) + sess.cmd("chord key 0", settle=0.2) + cur_xp = None + for key_name, xp, key, chord, notes in CASES: + if xp != cur_xp: + sess.cmd_ack(f"chord xpose {xp}", "CHORD_XPOSE_OK", timeout_s=2.0, retries=4) + cur_xp = xp + sess.cmd_ack(f"chord key {key}", "CHORD_KEY_OK", timeout_s=3.0, retries=4) + time.sleep(0.15) + print(f"\n-- {key_name} {chord} key={key} xp={xp}", flush=True) + for s in range(6, 0, -1): + expect = int(notes[6 - s]) + n0 = len(sess.lines) + sess.cmd(f"string {s}", settle=0.12) + sess.cmd(f"string {s} off", settle=0.05) + got = last_string_note("\n".join(sess.lines[n0:]), s) + ok = got == expect + if not ok: + fails += 1 + print( + f" str{s} expect={expect} got={got} {'PASS' if ok else 'FAIL'}", + flush=True, + ) + sess.cmd_ack("chord xpose 0", "CHORD_XPOSE_OK", timeout_s=2.0, retries=2) + sess.cmd("chord key 0", settle=0.2) + print(f"\nFAIL={fails} / {len(CASES) * 6}", flush=True) + return 1 if fails else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/test_tone_voicing.py b/tools/test_tone_voicing.py index 411d11b..4f13c6b 100644 --- a/tools/test_tone_voicing.py +++ b/tools/test_tone_voicing.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """音色表六弦音高校对:RTT string 回报 + 麦克风基频。 -对照 Doc/Bug/音色改善.png(C 调、移调 0)。 -I C / II Dm 保持原把位;III Em / IV F / V G / VI Am 为上移后的把位。 +对照 Doc/音色文件/自动伴奏新更改和弦规则.xlsx「C 大调」弦音高列(C 调、移调 0)。 +I C / II Dm 与旧表一致;III Em / IV F / V G / VI Am 按新表改为开放把位音高。 只走 `string N`,不再额外 midi on 期望音,避免麦测被兜底音骗过。 """ from __future__ import annotations @@ -38,10 +38,10 @@ except ImportError as exc: CASES = [ ("C", 1, (43, 48, 52, 55, 60, 64)), ("Dm", 4, (41, 45, 50, 57, 62, 65)), - ("Em", 7, (40, 55, 59, 64, 59, 64)), - ("F", 10, (41, 57, 60, 65, 60, 65)), - ("G", 13, (43, 47, 55, 59, 67, 67)), - ("Am", 16, (40, 45, 57, 60, 64, 64)), + ("Em", 7, (40, 47, 52, 55, 59, 64)), + ("F", 10, (41, 48, 53, 57, 60, 65)), + ("G", 13, (43, 47, 50, 55, 59, 67)), + ("Am", 16, (40, 45, 52, 57, 60, 64)), ] SR = 48000