fix(accomp): delay a same-tick arpeggio note by one eighth

The circled Em notes were stacked on the previous eighth. Hold only that extra note, and time it from the millisecond clock that actually runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yuquanjun 2026-10-16 12:08:29 +08:00
parent 3c81607daf
commit 927509ceb5
4 changed files with 138 additions and 21 deletions

View File

@ -532,6 +532,18 @@ uint8_t app_log_try_command(const char *cmd)
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
return 1U;
}
/* mode N:进入模式选择页的第 N 项(0 万能 / 1 专业 / 2 普通),供 RTT 起奏 */
if (strncmp(cmd, "mode ", 5) == 0 && cmd[5] != '\0') {
unsigned id = (unsigned)strtoul(cmd + 5, NULL, 10);
extern void Touch_Mode_Select_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex);
if (id > 2u) {
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "MODE_BAD\n");
return 1U;
}
Touch_Mode_Select_Action(0, (uint8_t)id, 0);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "MODE_OK\n");
return 1U;
}
/* tone start:在当前已加载音色上拨片起奏(不改 TAB/库) */
if (strncmp(cmd, "tone start", 10) == 0) {
StartFlag = 0;

View File

@ -56,8 +56,67 @@ static uint8_t s_seq_type;
static uint8_t s_seq_xp;
static uint8_t s_seq_po;
#define SEQ_SLOTS 12
#define ARP_CH 12 /* 逐音琶音;垫音通道同一拍会叠多个音,不参与网格 */
static uint8_t s_seq_step[16];
static struct { uint8_t ch, in, out, used; } s_seq_slot[SEQ_SLOTS];
/* 红圈:同一拍叠上来的音向右 1 个音(图上 1 tik = 两个格子 = 一个八分音符)。 */
static uint32_t s_grid_t0;
static uint16_t s_grid_unit;
static uint8_t s_grid_on;
static uint16_t s_pending_hold_ms;
static uint8_t s_slot_hold4[SEQ_SLOTS];
static volatile uint16_t s_hold_report;
static volatile uint8_t s_hold_report_ready;
static volatile uint8_t s_hold_count;
/* tmr6_ticks 没有在中断里累加,用已经在走的 1ms 时基。 */
extern uint32_t wk_timebase_get(void);
static void App_Auto_GridReset(void)
{
s_grid_on = 0;
s_pending_hold_ms = 0;
}
/* 当前速度的八分音符,单位 ms。速度范围 40..260。 */
static uint16_t App_Auto_EighthMs(void)
{
uint16_t bpm = (uint16_t)mGuiData[GUI_SPEED].Current;
if (bpm < 40u)
bpm = 40u;
return (uint16_t)(30000u / bpm);
}
static void App_Auto_QueueMidi(MidiFifoItem_t *m, uint16_t hold_ms)
{
if (hold_ms == 0u)
m->delayCounter = 0;
else
{
uint16_t due = (uint16_t)((uint16_t)wk_timebase_get() + hold_ms);
if (due == 0u)
due = 1u;
m->delayCounter = due;
s_hold_report = hold_ms;
if (s_hold_count < 255u)
s_hold_count++;
s_hold_report_ready = 1u;
}
mymidififo_InQueue(&m_MidiSendFifo, m);
}
void App_Auto_LogHeldNotes(void)
{
uint16_t ms;
uint8_t n;
if (!s_hold_report_ready)
return;
ms = s_hold_report;
n = s_hold_count;
s_hold_report_ready = 0;
LOG_I("SEQHOLD", "dly=%u n=%u unit=%u",
(unsigned)ms, (unsigned)n, (unsigned)s_grid_unit);
}
static void App_Auto_SeqRefresh(void)
{
@ -86,6 +145,7 @@ static void App_Auto_SeqRefresh(void)
if (s_seq_on)
App_Auto_QueueAllNotesOffMelodic();
s_seq_on = 0;
App_Auto_GridReset();
for (i = 0u; i < SEQ_SLOTS; i++)
s_seq_slot[i].used = 0;
return;
@ -106,7 +166,11 @@ static void App_Auto_SeqRefresh(void)
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_slot_hold4[i] = 0;
}
App_Auto_GridReset();
s_seq_on = 1u;
}
@ -116,6 +180,7 @@ static int App_Auto_MapSeq(int channel, int key, int is_on, int *out)
int ch;
int i;
s_pending_hold_ms = 0;
App_Auto_SeqRefresh();
if (!s_seq_on || channel == BASS_CH || channel == DRUM_CH)
return 0;
@ -128,15 +193,32 @@ static int App_Auto_MapSeq(int channel, int key, int is_on, int *out)
&& s_seq_slot[i].in == (uint8_t)key)
{
*out = s_seq_slot[i].out;
s_pending_hold_ms = (uint16_t)s_slot_hold4[i] * 4u;
s_seq_slot[i].used = 0;
s_slot_hold4[i] = 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);
{
uint8_t step = (uint8_t)(s_seq_step[ch] & 7u);
uint32_t now;
*out = s_seq[step];
s_seq_step[ch] = (uint8_t)((step + 1u) & 7u);
/* 琶音通道同一拍里的后一个音,晚一个八分音符再发出 */
if (ch == ARP_CH)
{
now = wk_timebase_get();
if (s_grid_on && (now - s_grid_t0) < (uint32_t)(s_grid_unit / 4u))
s_pending_hold_ms = s_grid_unit;
else
s_grid_unit = App_Auto_EighthMs();
s_grid_t0 = now;
s_grid_on = 1u;
}
}
for (i = 0; i < SEQ_SLOTS; i++)
{
if (!s_seq_slot[i].used)
@ -145,6 +227,7 @@ static int App_Auto_MapSeq(int channel, int key, int is_on, int *out)
s_seq_slot[i].ch = (uint8_t)ch;
s_seq_slot[i].in = (uint8_t)key;
s_seq_slot[i].out = (uint8_t)(*out);
s_slot_hold4[i] = (uint8_t)(s_pending_hold_ms / 4u);
return 1;
}
}
@ -152,6 +235,7 @@ static int App_Auto_MapSeq(int channel, int key, int is_on, int *out)
s_seq_slot[0].ch = (uint8_t)ch;
s_seq_slot[0].in = (uint8_t)key;
s_seq_slot[0].out = (uint8_t)(*out);
s_slot_hold4[0] = (uint8_t)(s_pending_hold_ms / 4u);
return 1;
}
@ -193,7 +277,7 @@ static uint8_t App_Auto_RegFingerprint(void)
static void App_Auto_QueueAllNotesOffMelodic(void)
{
MidiFifoItem_t m;
MidiFifoItem_t m = {0};
int ch;
for (ch = 0; ch < 16; ch++)
{
@ -202,7 +286,7 @@ static void App_Auto_QueueAllNotesOffMelodic(void)
m.msg[1] = 123; /* All Notes Off */
m.msg[2] = 0;
m.msglen = 3;
mymidififo_InQueue(&m_MidiSendFifo, &m);
App_Auto_QueueMidi(&m, 0);
}
}
@ -492,7 +576,7 @@ static int App_Auto_MapNoteOn(int channel, int key)
static void Func_CallBack_ProgramChange(int channel, int program)
{
MidiFifoItem_t midimsg;
MidiFifoItem_t midimsg = {0};
uint8_t ch = (uint8_t)(channel & 0x0F);
uint8_t bank = MIDI_BANK_ACCOMP;
/* 通道 0 是硅胶弦。节奏预设常在这里放钢琴(Program 0),会盖掉弦音色。 */
@ -510,33 +594,33 @@ static void Func_CallBack_ProgramChange(int channel, int program)
midimsg.msg[1] = 0x00; /* CC0 Bank MSB */
midimsg.msg[2] = bank;
midimsg.msglen = 3;
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
App_Auto_QueueMidi(&midimsg, 0);
midimsg.msg[1] = 0x20; /* CC32 Bank LSB */
midimsg.msg[2] = 0;
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
App_Auto_QueueMidi(&midimsg, 0);
midimsg.msg[0] = 0xc0 | ch;
midimsg.msg[1] = (uint8_t)program;
midimsg.msg[2] = 0;
midimsg.msglen = 2;
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
App_Auto_QueueMidi(&midimsg, 0);
}
static void Func_CallBack_NoteOff(int channel,int key)
{
MidiFifoItem_t midimsg;
MidiFifoItem_t midimsg = {0};
uint8_t flags = 0;
int out_key = App_Auto_MapNote(channel, key, &flags, 0);
midimsg.msg[0] = 0x80 | (channel & 0x0F);
midimsg.msg[1] = (uint8_t)out_key;
midimsg.msg[2] = 0;
midimsg.msglen = 3;
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
App_Auto_QueueMidi(&midimsg, s_pending_hold_ms);
}
static void Func_CallBack_NoteOn(int channel,int key,int vel)
{
MidiFifoItem_t midimsg;
MidiFifoItem_t midimsg = {0};
int out_key;
if (vel == 0)
@ -551,7 +635,7 @@ static void Func_CallBack_NoteOn(int channel,int key,int vel)
midimsg.msg[1] = (uint8_t)out_key;
midimsg.msg[2] = (uint8_t)vel;
midimsg.msglen = 3;
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
App_Auto_QueueMidi(&midimsg, s_pending_hold_ms);
}
@ -561,23 +645,23 @@ static void Func_CallBack_CC(int channel,int cc,int value)
{
return;
}
MidiFifoItem_t midimsg;
MidiFifoItem_t midimsg = {0};
midimsg.msg[0] = 0xb0 | channel;
midimsg.msg[1] = cc;
midimsg.msg[2] = value;
midimsg.msglen = 3;
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
App_Auto_QueueMidi(&midimsg, 0);
}
static void Func_CallBack_PitchWheel(int channel,int cc,int value)
{
MidiFifoItem_t midimsg;
MidiFifoItem_t midimsg = {0};
midimsg.msg[0] = 0xe0 | channel;
midimsg.msg[1] = cc;
midimsg.msg[2] = value;
midimsg.msglen = 3;
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
App_Auto_QueueMidi(&midimsg, 0);
}
/* Flash Map 0903:对照 ExtFlash_Tone_Addr.h / Doc/.../FLASH_MAP.txt */

View File

@ -185,7 +185,7 @@
<state>USE_STDPERIPH_DRIVER</state>
<state>AT_START_F403A_V1</state>
<state>autoband</state>
<state>DEBUG_LCD_DUMP=1</state>
<state>DEBUG_LCD_DUMP=0</state>
</option>
<option>
<name>CCPreprocFile</name>

View File

@ -222,15 +222,36 @@ void TaskBTRecvThread_entry(void* parameter)
}
}
extern void App_Auto_LogHeldNotes(void);
void TaskAutobandThread_entry(void* parameter)
{
for(;;)
{
while(!mymidififo_IsQueueEmpty(&m_MidiSendFifo))
int guard = mymidififo_GetQueueLength(&m_MidiSendFifo);
uint16_t now = (uint16_t)wk_timebase_get();
while (guard-- > 0 && !mymidififo_IsQueueEmpty(&m_MidiSendFifo))
{
MidiFifoItem_t* pitem = mymidififo_DeQueue(&m_MidiSendFifo);
SendMidiDataToDreamDSP(pitem->msg, pitem->msglen);
//ResetAutoPowerCount();
MidiFifoItem_t* peek = mymidififo_TryDeQueue(&m_MidiSendFifo);
MidiFifoItem_t item;
uint16_t due;
uint16_t ahead;
if (peek == 0)
break;
item = *peek;
due = item.delayCounter;
ahead = (uint16_t)(due - now);
/* delayCounter==0 立即发送;未到点的音挪到队尾,避免挡住鼓和低音 */
if (due != 0u && ahead != 0u && ahead < 0x8000u)
{
mymidififo_DeQueue(&m_MidiSendFifo);
mymidififo_InQueue(&m_MidiSendFifo, &item);
continue;
}
mymidififo_DeQueue(&m_MidiSendFifo);
SendMidiDataToDreamDSP(item.msg, item.msglen);
if (due != 0u)
App_Auto_LogHeldNotes();
}
rt_thread_mdelay(1);
}