fix(chord): play sheet accompaniment sequences and chord tones

III-VII shapes use the C-major sequence column with the same fixed-key offset as the strings. Chord tones follow the decimal column. Bump firmware to 0.2.57.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yuquanjun 2026-10-15 19:22:17 +08:00
parent 99c2d84465
commit 3c81607daf
5 changed files with 273 additions and 18 deletions

View File

@ -583,6 +583,38 @@ uint8_t app_log_try_command(const char *cmd)
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "TONE_PICK_DONE\n"); SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "TONE_PICK_DONE\n");
return 1U; return 1U;
} }
/* chord notes KEY [TYPE]:当前移调下 GetChordNotesByType 的和弦音 */
if (strncmp(cmd, "chord notes ", 12) == 0 && cmd[12] != '\0') {
char *endp = NULL;
unsigned key = (unsigned)strtoul(cmd + 12, &endp, 10);
unsigned type = 0;
int has_type = 0;
uint8_t buff[4] = {0, 0, 0, 0};
uint8_t n, i;
char line[64];
int pos;
if (endp != NULL && *endp == ' ') {
type = (unsigned)strtoul(endp + 1, NULL, 10);
has_type = 1;
}
if (key < 1U || key > 21U) {
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "CHORD_NOTES_BAD\n");
return 1U;
}
if (!has_type)
type = chord_type_index_map[key].type;
n = GetChordNotesByType((uint8_t)key, (uint8_t)type, buff);
pos = snprintf(line, sizeof(line), "CHORD_NOTES_OK key=%u type=%u n=%u",
key, type, (unsigned)n);
for (i = 0; i < n && pos > 0 && pos < (int)sizeof(line) - 5; i++)
pos += snprintf(line + pos, sizeof(line) - (size_t)pos, " %u", (unsigned)buff[i]);
if (pos > 0 && pos < (int)sizeof(line) - 2) {
line[pos++] = '\n';
line[pos] = '\0';
}
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
return 1U;
}
/* 测试注入:chord key N(0释放 / 1~21和弦 / 22拍速 / 23停止) */ /* 测试注入:chord key N(0释放 / 1~21和弦 / 22拍速 / 23停止) */
if (strncmp(cmd, "chord key ", 10) == 0 && cmd[10] != '\0') { if (strncmp(cmd, "chord key ", 10) == 0 && cmd[10] != '\0') {
unsigned key = (unsigned)strtoul(cmd + 10, NULL, 10); unsigned key = (unsigned)strtoul(cmd + 10, NULL, 10);

View File

@ -88,6 +88,109 @@ void FretChord_FillUseMidi(uint8_t key)
USE_MIDI.Midi_6 = (uint8_t)(ORGAN_MIDI[type].Midi_6 + add); USE_MIDI.Midi_6 = (uint8_t)(ORGAN_MIDI[type].Midi_6 + add);
} }
/* C 大调「和弦伴奏发音顺序」,[源品位 I..VII][性质 0..8][8 音]。
* 0 表示该格留空,改用标准型指法从六弦生成。AM7 格原文「#C4# G3」按同列写成
* A2 #G3 #C4 #G3 E4 #G3 #C4 #G3。I/II 形状不走这张表(表上不需要改)。 */
static const uint8_t ACCOMP_SEQ[7][9][8] =
{
{ /* I C */
{48,55,60,55,64,55,60,55}, {48,55,60,55,63,55,60,55},
{48,55,70,55,63,55,70,55}, {48,55,70,55,64,55,70,55},
{48,55,71,55,64,55,71,55}, {0}, {0}, {0}, {0},
},
{ /* II D */
{50,57,62,57,66,57,62,57}, {50,57,62,57,65,57,62,57},
{50,57,60,57,65,57,62,57}, {50,57,60,57,66,57,60,57},
{50,57,61,57,66,57,61,57}, {0}, {0}, {0}, {0},
},
{ /* III E */
{40,56,59,56,64,56,59,56}, {40,55,59,55,64,55,59,55},
{40,55,59,55,62,55,59,55}, {40,56,59,56,62,56,59,56},
{40,56,59,56,63,56,59,56}, {0}, {0}, {0}, {0},
},
{ /* IV F */
{41,57,60,57,65,57,60,57}, {41,56,60,56,65,56,60,56},
{41,56,60,56,63,56,60,56}, {41,57,60,57,63,57,60,57},
{41,57,60,57,64,57,60,57}, {0}, {0}, {0}, {0},
},
{ /* V G */
{43,50,59,50,67,50,59,50}, {43,50,58,50,67,50,58,50},
{43,50,58,50,65,50,58,50}, {43,50,59,50,65,50,59,50},
{43,50,59,50,66,50,59,50}, {0}, {0}, {0}, {0},
},
{ /* VI A */
{45,57,61,57,64,57,61,57}, {45,57,60,57,64,57,60,57},
{45,55,60,55,64,55,60,55}, {45,55,61,55,64,55,61,55},
{45,56,61,56,64,56,61,56}, {0}, {0}, {0}, {0},
},
{ /* VII B */
{47,59,63,59,66,59,63,59}, {47,59,62,59,66,59,62,59},
{47,54,57,54,62,54,57,54}, {47,54,57,54,63,54,57,54},
{47,54,58,54,63,54,58,54}, {0}, {0}, {0}, {0},
},
};
/* Sheet2 标准型指法,弦号 6=低音弦。只给发音顺序留空的性质用。 */
static const uint8_t ACCOMP_FINGER[7][8] =
{
{5,3,2,3,1,3,2,3},
{4,3,2,3,1,3,2,3},
{6,3,2,3,1,3,2,3},
{6,3,2,3,1,3,2,3},
{6,3,2,3,1,3,2,3},
{5,3,2,3,1,3,2,3},
{5,3,2,3,1,3,2,3},
};
static uint8_t Accomp_StringMidi(const STRING_MIDI *row, uint8_t string_no)
{
switch (string_no)
{
case 1: return row->Midi_1;
case 2: return row->Midi_2;
case 3: return row->Midi_3;
case 4: return row->Midi_4;
case 5: return row->Midi_5;
default: return row->Midi_6;
}
}
uint8_t AccompPattern_Fill(uint8_t key, uint8_t out[8])
{
uint8_t group, src, add = 0, type, i;
const uint8_t *seq = 0;
if (key < 1u || key > 21u)
return 0;
group = (uint8_t)((key - 1u) / 3u);
src = FixedKey_SourceFret(group, &add);
if (src < 2u)
return 0;
if (chord_type_index_map[key].PitchOffset == 2u)
add = (uint8_t)(add + 1u);
type = chord_type_index_map[key].type;
if (type < 9u && ACCOMP_SEQ[src][type][0] != 0u)
seq = ACCOMP_SEQ[src][type];
for (i = 0u; i < 8u; i++)
{
uint8_t n;
if (seq)
n = seq[i];
else
{
const STRING_MIDI *row = OrganChord_Midi_Table[src];
uint8_t t = type;
if (src < 6u && t > 8u)
t = 0u;
if (src == 6u && t > 9u)
t = 0u;
n = Accomp_StringMidi(&row[t], ACCOMP_FINGER[src][i]);
}
n = (uint8_t)(n + add);
out[i] = (n > 127u) ? 127u : n;
}
return 1u;
}
const LEBEL_UI UI_Label[] = { const LEBEL_UI UI_Label[] = {
@ -169,7 +272,7 @@ int16_t MIC_VOL_MAP[11]= {-9000,-7000,-5000,-3000,-1000,1000,3000,5000,7000,9000
uint8_t TOUCH_AREA_NUM = (sizeof(Touch_Areas)/sizeof(Touch_AreaTypeDef)); uint8_t TOUCH_AREA_NUM = (sizeof(Touch_Areas)/sizeof(Touch_AreaTypeDef));
const uint8_t Version[3] = {0,2,56}; /* major.minor.patch — 0.2.56: 固定调和弦音高 */ const uint8_t Version[3] = {0,2,57}; /* major.minor.patch — 0.2.57: 伴奏发音顺序与和弦音高 */
uint8_t Led = 8; uint8_t Led = 8;
/* App 已下发琴颈引导灯:拨片勿再全绿盖掉 */ /* App 已下发琴颈引导灯:拨片勿再全绿盖掉 */
static uint8_t s_app_neck_led = 0; static uint8_t s_app_neck_led = 0;
@ -2222,14 +2325,14 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff)
break; break;
} }
break; break;
case 2: /* III:E 品位(整组低八度;M/m 三和弦带 E4) */ case 2: /* III:E 品位(整组低八度;三和弦不再另加 E4) */
switch(type) switch(type)
{ {
case 0: case 0:
buff[0] = 40;buff[1] = 44;buff[2] = 47;buff[3] = 64; buff[0] = 40;buff[1] = 44;buff[2] = 47;
break; break;
case 1: case 1:
buff[0] = 40;buff[1] = 43;buff[2] = 47;buff[3] = 64; buff[0] = 40;buff[1] = 43;buff[2] = 47;
break; break;
case 2: case 2:
buff[0] = 40;buff[1] = 43;buff[2] = 47;buff[3] = 50; buff[0] = 40;buff[1] = 43;buff[2] = 47;buff[3] = 50;
@ -2350,26 +2453,26 @@ uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff)
break; break;
} }
break; break;
case 6: /* VII:B 品位(Bm/B7/BM7/Bad9/Bm7-5 按新表改) */ case 6: /* VII:B 品位(五音为 #F;Bad9 最高音为 B) */
switch(type) switch(type)
{ {
case 0: case 0:
buff[0] = 47;buff[1] = 51;buff[2] = 53; buff[0] = 47;buff[1] = 51;buff[2] = 54;
break; break;
case 1: case 1:
buff[0] = 47;buff[1] = 50;buff[2] = 53; buff[0] = 47;buff[1] = 50;buff[2] = 54;
break; break;
case 2: case 2:
buff[0] = 47;buff[1] = 50;buff[2] = 54;buff[3] = 57; buff[0] = 47;buff[1] = 50;buff[2] = 54;buff[3] = 57;
break; break;
case 3: case 3:
buff[0] = 47;buff[1] = 51;buff[2] = 53;buff[3] = 57; buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 57;
break; break;
case 4: case 4:
buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 58; buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 58;
break; break;
case 5: case 5:
buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 60; buff[0] = 47;buff[1] = 51;buff[2] = 54;buff[3] = 59;
break; break;
case 6: case 6:
buff[0] = 47;buff[1] = 50;buff[2] = 53;buff[3] = 57; buff[0] = 47;buff[1] = 50;buff[2] = 53;buff[3] = 57;

View File

@ -277,6 +277,8 @@ uint8_t AppNeckLed_IsGuide(void);
uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff); uint8_t GetChordNotesByType(uint8_t MIDI_Index,uint8_t type,uint8_t *buff);
void FretChord_FillUseMidi(uint8_t key); void FretChord_FillUseMidi(uint8_t key);
/* 源品位 III~VII:按 C 大调「和弦伴奏发音顺序」填 8 音(已含固定调半音)。I/II 返回 0 */
uint8_t AccompPattern_Fill(uint8_t key, uint8_t out[8]);
/* 伴奏已在播时仅更新和弦音,不 Restart(快转调) */ /* 伴奏已在播时仅更新和弦音,不 Restart(快转调) */
void Accomp_UpdatePlayingChord(void); void Accomp_UpdatePlayingChord(void);
/* 切模式/停键前记住专业移调与最后指板和弦 */ /* 切模式/停键前记住专业移调与最后指板和弦 */

View File

@ -23,10 +23,11 @@ static uint8_t pStoreBuffer[PRESET_BUFFER_SIZE];
* 代码通道(0 起,与 MIDI status 低 4 位一致):8=bass,9=鼓;其余=和弦。 * 代码通道(0 起,与 MIDI status 低 4 位一致):8=bass,9=鼓;其余=和弦。
* 日志打印同号(ch8=bass)。 * 日志打印同号(ch8=bass)。
* ① 指板 I/II(键1~6):原样;移调≥#F(6) 时表格「整体-12」(bass、和弦)。 * ① 指板 I/II(键1~6):原样;移调≥#F(6) 时表格「整体-12」(bass、和弦)。
* ② 指板 III/IV(键7~12):bass -12 地板 E1;和弦 -12 夹在 E2~#G4; * ② 指板 III/IV(键7~12):bass -12 地板 E1;和弦通道改走发音顺序(见下)。
* 不再叠加转调 -12(避免 G 调 III 把 B3 压回 B2 后与根音重叠)。 * ③ 指板 V~VII(键13~21):bass 同②,移调≥#F 时 bass 再 -12。
* ③ 指板 V~VII(键13~21):同②的把位 -12;移调≥#F 时再叠加 -12。 * 和弦通道:源品位是 III~VII 时,不再做 -12,按 C 大调「和弦伴奏发音顺序」
* 对照 Doc/吉他和弦表最终版.xlsx / 转调说明表 */ * 逐音循环(固定调半音与六弦同一套映射)。I/II 形状仍走上面的八度规则。
* 对照 自动伴奏新更改和弦规则.xlsx「C大调 / 和弦伴奏发音顺序」 */
#define CHORD_REG_FIX_EN 1 #define CHORD_REG_FIX_EN 1
#define BASS_CH 8 /* 日志 ch8 */ #define BASS_CH 8 /* 日志 ch8 */
#define DRUM_CH 9 /* 日志 ch9 */ #define DRUM_CH 9 /* 日志 ch9 */
@ -44,6 +45,116 @@ static uint16_t s_ch_ever_mask = 0; /* 会话内各通道是否出现过 NoteOn
#define PITCH_LOG_PER_FP 24 #define PITCH_LOG_PER_FP 24
#define BASS_LOG_PER_FP 16 #define BASS_LOG_PER_FP 16
static void App_Auto_QueueAllNotesOffMelodic(void);
/* 和弦通道发音顺序:每通道自己的拍点,NoteOff 按进入音配对 */
static uint8_t s_seq[8];
static uint8_t s_seq_on;
static uint8_t s_seq_ready;
static uint8_t s_seq_key;
static uint8_t s_seq_type;
static uint8_t s_seq_xp;
static uint8_t s_seq_po;
#define SEQ_SLOTS 12
static uint8_t s_seq_step[16];
static struct { uint8_t ch, in, out, used; } s_seq_slot[SEQ_SLOTS];
static void App_Auto_SeqRefresh(void)
{
uint8_t key = KEY_ID_1629;
uint8_t type = 0, po = 0;
uint8_t xp = (uint8_t)mGuiData[GUI_TRANSPOSE].Current;
uint8_t seq[8];
uint8_t on;
uint8_t i;
if (key >= 1u && key <= 21u)
{
type = chord_type_index_map[key].type;
po = chord_type_index_map[key].PitchOffset;
}
if (s_seq_ready && key == s_seq_key && type == s_seq_type && xp == s_seq_xp && po == s_seq_po)
return;
on = AccompPattern_Fill(key, seq);
s_seq_key = key;
s_seq_type = type;
s_seq_xp = xp;
s_seq_po = po;
s_seq_ready = 1u;
if (!on)
{
if (s_seq_on)
App_Auto_QueueAllNotesOffMelodic();
s_seq_on = 0;
for (i = 0u; i < SEQ_SLOTS; i++)
s_seq_slot[i].used = 0;
return;
}
if (s_seq_on)
{
uint8_t same = 1u;
for (i = 0u; i < 8u; i++)
{
if (s_seq[i] != seq[i])
same = 0u;
}
if (!same)
App_Auto_QueueAllNotesOffMelodic();
}
for (i = 0u; i < 8u; i++)
s_seq[i] = seq[i];
for (i = 0u; i < 16u; i++)
s_seq_step[i] = 0;
for (i = 0u; i < SEQ_SLOTS; i++)
s_seq_slot[i].used = 0;
s_seq_on = 1u;
}
/* 1 = 已用发音顺序处理(out 为最终音高) */
static int App_Auto_MapSeq(int channel, int key, int is_on, int *out)
{
int ch;
int i;
App_Auto_SeqRefresh();
if (!s_seq_on || channel == BASS_CH || channel == DRUM_CH)
return 0;
ch = channel & 15;
if (!is_on)
{
for (i = SEQ_SLOTS - 1; i >= 0; i--)
{
if (s_seq_slot[i].used && s_seq_slot[i].ch == (uint8_t)ch
&& s_seq_slot[i].in == (uint8_t)key)
{
*out = s_seq_slot[i].out;
s_seq_slot[i].used = 0;
return 1;
}
}
*out = key;
return 1;
}
*out = s_seq[s_seq_step[ch] & 7u];
s_seq_step[ch] = (uint8_t)((s_seq_step[ch] + 1u) & 7u);
for (i = 0; i < SEQ_SLOTS; i++)
{
if (!s_seq_slot[i].used)
{
s_seq_slot[i].used = 1;
s_seq_slot[i].ch = (uint8_t)ch;
s_seq_slot[i].in = (uint8_t)key;
s_seq_slot[i].out = (uint8_t)(*out);
return 1;
}
}
s_seq_slot[0].used = 1;
s_seq_slot[0].ch = (uint8_t)ch;
s_seq_slot[0].in = (uint8_t)key;
s_seq_slot[0].out = (uint8_t)(*out);
return 1;
}
static int App_Auto_ClampMidi(int n) static int App_Auto_ClampMidi(int n)
{ {
if (n < 0) return 0; if (n < 0) return 0;
@ -229,7 +340,7 @@ static int App_Auto_MapXposeOverall(int channel, int key, uint8_t *flags)
return App_Auto_ClampMidi(out); return App_Auto_ClampMidi(out);
} }
static int App_Auto_MapNote(int channel, int key, uint8_t *flags) static int App_Auto_MapNote(int channel, int key, uint8_t *flags, int is_on)
{ {
int out = key; int out = key;
if (flags) *flags = 0; if (flags) *flags = 0;
@ -238,6 +349,12 @@ static int App_Auto_MapNote(int channel, int key, uint8_t *flags)
if (channel == DRUM_CH || key < 0 || key > 127) if (channel == DRUM_CH || key < 0 || key > 127)
return key; return key;
if (App_Auto_MapSeq(channel, key, is_on, &out))
{
if (flags) *flags = 0x20u; /* 发音顺序 */
return out;
}
if (App_Auto_NeedRegisterFix()) if (App_Auto_NeedRegisterFix())
{ {
/* III+:把位音区修正;V~VII 且 ≥#F 时 Map* 内再叠转调 -12 */ /* III+:把位音区修正;V~VII 且 ≥#F 时 Map* 内再叠转调 -12 */
@ -258,6 +375,7 @@ static int App_Auto_MapNote(int channel, int key, uint8_t *flags)
} }
#else #else
(void)channel; (void)channel;
(void)is_on;
#endif #endif
return out; return out;
} }
@ -353,7 +471,7 @@ static int App_Auto_MapNoteOn(int channel, int key)
s_ch_ever_mask = 0; /* 新策略周期重新报到通道 */ s_ch_ever_mask = 0; /* 新策略周期重新报到通道 */
} }
out = App_Auto_MapNote(channel, key, &flags); out = App_Auto_MapNote(channel, key, &flags, 1);
if (channel >= 0 && channel < 16) if (channel >= 0 && channel < 16)
{ {
uint16_t bit = (uint16_t)(1u << channel); uint16_t bit = (uint16_t)(1u << channel);
@ -407,7 +525,7 @@ static void Func_CallBack_NoteOff(int channel,int key)
{ {
MidiFifoItem_t midimsg; MidiFifoItem_t midimsg;
uint8_t flags = 0; uint8_t flags = 0;
int out_key = App_Auto_MapNote(channel, key, &flags); int out_key = App_Auto_MapNote(channel, key, &flags, 0);
midimsg.msg[0] = 0x80 | (channel & 0x0F); midimsg.msg[0] = 0x80 | (channel & 0x0F);
midimsg.msg[1] = (uint8_t)out_key; midimsg.msg[1] = (uint8_t)out_key;
midimsg.msg[2] = 0; midimsg.msg[2] = 0;
@ -424,7 +542,7 @@ static void Func_CallBack_NoteOn(int channel,int key,int vel)
if (vel == 0) if (vel == 0)
{ {
uint8_t flags = 0; uint8_t flags = 0;
out_key = App_Auto_MapNote(channel, key, &flags); /* 视同 NoteOff */ out_key = App_Auto_MapNote(channel, key, &flags, 0); /* 视同 NoteOff */
} }
else else
out_key = App_Auto_MapNoteOn(channel, key); out_key = App_Auto_MapNoteOn(channel, key);

View File

@ -38,7 +38,7 @@ TONE_HDR = PROJ / "project" / "inc" / "ExtFlash_Tone_Addr.h"
IAP_DIR = REPO / "升级" / "MCU主控升级" IAP_DIR = REPO / "升级" / "MCU主控升级"
OUT_ROOT = REPO / "tools" / "out" OUT_ROOT = REPO / "tools" / "out"
FW_VER = "0.2.56" FW_VER = "0.2.57"
UI0902_RES_BASE = 0x00100000 UI0902_RES_BASE = 0x00100000
BOOT_FLASH_ADDR = 0x08000000 BOOT_FLASH_ADDR = 0x08000000
APP_FLASH_ADDR = 0x08008000 APP_FLASH_ADDR = 0x08008000