510 lines
15 KiB
C
510 lines
15 KiB
C
/*
|
||
* App_Auto.c
|
||
*
|
||
* Created on: Apr 16, 2026
|
||
* Author: monosail
|
||
*/
|
||
|
||
|
||
#include "autobandtop1.h"
|
||
#include <stdint.h>
|
||
#include "output2.bin.h"
|
||
#include "mymidififo1.h"
|
||
#include <stdlib.h>
|
||
#include "includes.h"
|
||
MidiFifoSeq_t m_MidiSendFifo;
|
||
#define PRESET_BUFFER_SIZE (1024*41) /* 2s.res compressed HKTK preset 39760 bytes */
|
||
static int m_presetCount = 0;
|
||
static uint8_t pStoreBuffer[PRESET_BUFFER_SIZE];
|
||
//static uint8_t* pStoreBuffer;
|
||
#define DEFAULT_TEMPO 100
|
||
|
||
/* ======== Bass/和弦分通道八度(无 AutoBand 源码时的输出侧映射) ========
|
||
* 代码通道(0 起,与 MIDI status 低 4 位一致):8=bass,9=鼓;其余=和弦。
|
||
* 日志打印同号(ch8=bass)。I/II(键1~6)原样;III+:bass -12 地板 E1;
|
||
* 和弦 -12 夹在 E2~#G4,超上限就近和弦内音。移调≥#F(6) 时再 -12。
|
||
* 对照 Doc/吉他和弦表最终版.xlsx */
|
||
#define CHORD_REG_FIX_EN 1
|
||
#define BASS_CH 8 /* 日志 ch8 */
|
||
#define DRUM_CH 9 /* 日志 ch9 */
|
||
#define MIDI_E1 28
|
||
#define MIDI_E2 40
|
||
#define MIDI_GSHARP4 68
|
||
#define XPOSE_FS 6 /* C=0 时 #F=6 */
|
||
|
||
/* 映射策略指纹:变化时 CC123 清旧音,防挂音(不建大音符表) */
|
||
static uint8_t s_reg_fp = 0xFFu;
|
||
/* 每个指纹周期内 NoteOn 映射日志限额(和弦与 bass 分开,避免和弦占满看不到 bass) */
|
||
static uint8_t s_pitch_log_left = 0;
|
||
static uint8_t s_bass_log_left = 0;
|
||
static uint16_t s_ch_ever_mask = 0; /* 会话内各通道是否出现过 NoteOn */
|
||
#define PITCH_LOG_PER_FP 24
|
||
#define BASS_LOG_PER_FP 16
|
||
|
||
static int App_Auto_ClampMidi(int n)
|
||
{
|
||
if (n < 0) return 0;
|
||
if (n > 127) return 127;
|
||
return n;
|
||
}
|
||
|
||
static int App_Auto_ChordDegree(void)
|
||
{
|
||
if (KEY_ID_1629 < 1 || KEY_ID_1629 > 21)
|
||
return 0;
|
||
return (int)((KEY_ID_1629 - 1) / 3) + 1; /* 1=I .. 7=VII */
|
||
}
|
||
|
||
static int App_Auto_NeedRegisterFix(void)
|
||
{
|
||
/* 键 1~6 = I/II:不动;7~21 = III~VII */
|
||
return (KEY_ID_1629 >= 7 && KEY_ID_1629 <= 21) ? 1 : 0;
|
||
}
|
||
|
||
static int App_Auto_XposeExtra(void)
|
||
{
|
||
return (mGuiData[GUI_TRANSPOSE].Current >= XPOSE_FS) ? 1 : 0;
|
||
}
|
||
|
||
static uint8_t App_Auto_RegFingerprint(void)
|
||
{
|
||
/* bit0=need_fix, bit1=xpose_extra, bit2.. = 粗粒度级数 (key/3) */
|
||
uint8_t fp = 0;
|
||
if (App_Auto_NeedRegisterFix()) fp |= 0x01u;
|
||
if (App_Auto_XposeExtra()) fp |= 0x02u;
|
||
if (KEY_ID_1629 >= 1 && KEY_ID_1629 <= 21)
|
||
fp |= (uint8_t)(((KEY_ID_1629 - 1) / 3) << 2);
|
||
return fp;
|
||
}
|
||
|
||
static void App_Auto_QueueAllNotesOffMelodic(void)
|
||
{
|
||
MidiFifoItem_t m;
|
||
int ch;
|
||
for (ch = 0; ch < 16; ch++)
|
||
{
|
||
if (ch == DRUM_CH) continue;
|
||
m.msg[0] = (uint8_t)(0xB0 | ch);
|
||
m.msg[1] = 123; /* All Notes Off */
|
||
m.msg[2] = 0;
|
||
m.msglen = 3;
|
||
mymidififo_InQueue(&m_MidiSendFifo, &m);
|
||
}
|
||
}
|
||
|
||
/* 超出 #G4:在当前和弦内音(GetChordNotesByType,对照和弦表「和弦伴奏」列)中就近 */
|
||
static int App_Auto_NearestChordTone(int target)
|
||
{
|
||
uint8_t buff[4] = {0, 0, 0, 0};
|
||
uint8_t n;
|
||
uint8_t pcs[4];
|
||
int i, oct, best = -1, best_dist = 9999;
|
||
uint8_t type;
|
||
|
||
if (KEY_ID_1629 < 1 || KEY_ID_1629 > 21)
|
||
return App_Auto_ClampMidi(target);
|
||
|
||
type = chord_type_index_map[KEY_ID_1629].type;
|
||
n = GetChordNotesByType(KEY_ID_1629, type, buff);
|
||
if (n == 0)
|
||
return App_Auto_ClampMidi(target > MIDI_GSHARP4 ? MIDI_GSHARP4 : target);
|
||
|
||
for (i = 0; i < (int)n; i++)
|
||
pcs[i] = (uint8_t)(buff[i] % 12);
|
||
|
||
for (i = 0; i < (int)n; i++)
|
||
{
|
||
for (oct = 0; oct < 11; oct++)
|
||
{
|
||
int cand = (int)pcs[i] + oct * 12;
|
||
int dist;
|
||
if (cand < MIDI_E2 || cand > MIDI_GSHARP4)
|
||
continue;
|
||
dist = cand - target;
|
||
if (dist < 0) dist = -dist;
|
||
/* 并列:优先较低者(不超过上限) */
|
||
if (dist < best_dist || (dist == best_dist && (best < 0 || cand < best)))
|
||
{
|
||
best_dist = dist;
|
||
best = cand;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (best < 0)
|
||
return MIDI_GSHARP4;
|
||
return best;
|
||
}
|
||
|
||
static int App_Auto_MapBassNote(int key, uint8_t *flags)
|
||
{
|
||
int out = key - 12;
|
||
uint8_t f = 0x01u; /* bit0: -12 applied */
|
||
while (out < MIDI_E1)
|
||
{
|
||
out += 12;
|
||
f |= 0x02u; /* bit1: floored up to E1 */
|
||
}
|
||
if (App_Auto_XposeExtra())
|
||
{
|
||
out -= 12;
|
||
f |= 0x04u; /* bit2: xpose extra -12 */
|
||
while (out < MIDI_E1)
|
||
{
|
||
out += 12;
|
||
f |= 0x02u;
|
||
}
|
||
}
|
||
if (flags) *flags = f;
|
||
return App_Auto_ClampMidi(out);
|
||
}
|
||
|
||
static int App_Auto_MapChordNote(int key, uint8_t *flags)
|
||
{
|
||
int out = key - 12;
|
||
uint8_t f = 0x01u; /* bit0: -12 */
|
||
|
||
while (out < MIDI_E2)
|
||
{
|
||
out += 12;
|
||
f |= 0x08u; /* bit3: raised to E2 */
|
||
}
|
||
|
||
if (out > MIDI_GSHARP4)
|
||
{
|
||
out = App_Auto_NearestChordTone(out);
|
||
f |= 0x10u; /* bit4: nearest chord tone */
|
||
}
|
||
|
||
if (App_Auto_XposeExtra())
|
||
{
|
||
out -= 12;
|
||
f |= 0x04u;
|
||
while (out < MIDI_E2)
|
||
{
|
||
out += 12;
|
||
f |= 0x08u;
|
||
}
|
||
if (out > MIDI_GSHARP4)
|
||
{
|
||
out = App_Auto_NearestChordTone(out);
|
||
f |= 0x10u;
|
||
}
|
||
}
|
||
if (flags) *flags = f;
|
||
return App_Auto_ClampMidi(out);
|
||
}
|
||
|
||
static int App_Auto_MapNote(int channel, int key, uint8_t *flags)
|
||
{
|
||
int out = key;
|
||
if (flags) *flags = 0;
|
||
|
||
#if CHORD_REG_FIX_EN
|
||
if (channel == DRUM_CH || key < 0 || key > 127)
|
||
return key;
|
||
|
||
if (!App_Auto_NeedRegisterFix())
|
||
return key;
|
||
|
||
if (channel == BASS_CH)
|
||
out = App_Auto_MapBassNote(key, flags);
|
||
else
|
||
out = App_Auto_MapChordNote(key, flags);
|
||
#else
|
||
(void)channel;
|
||
#endif
|
||
return out;
|
||
}
|
||
|
||
static const char *App_Auto_RoleName(int channel)
|
||
{
|
||
if (channel == DRUM_CH) return "drum";
|
||
if (channel == BASS_CH) return "bass";
|
||
return "chord";
|
||
}
|
||
|
||
static void App_Auto_LogPitch(int channel, int key, int out, uint8_t flags, int is_on)
|
||
{
|
||
const char *role;
|
||
int deg;
|
||
int fix;
|
||
int xf;
|
||
int pass;
|
||
|
||
if (channel == DRUM_CH)
|
||
return;
|
||
|
||
if (channel == BASS_CH)
|
||
{
|
||
if (s_bass_log_left == 0)
|
||
return;
|
||
}
|
||
else if (s_pitch_log_left == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
role = App_Auto_RoleName(channel);
|
||
deg = App_Auto_ChordDegree();
|
||
fix = App_Auto_NeedRegisterFix();
|
||
xf = App_Auto_XposeExtra();
|
||
pass = (!fix || channel == DRUM_CH) ? 1 : 0;
|
||
|
||
LOG_I("PITCH", "%s ch%u(%s) %d->%d d=%d deg=%d chord=%u xp=%u xf=%d fl=0x%02X %s%s%s%s",
|
||
is_on ? "on" : "off",
|
||
(unsigned)channel, role, /* 0 基,与 BASS_CH=8 一致 */
|
||
key, out, out - key,
|
||
deg, (unsigned)KEY_ID_1629,
|
||
(unsigned)mGuiData[GUI_TRANSPOSE].Current, xf,
|
||
(unsigned)flags,
|
||
pass ? "PASS " : "",
|
||
(flags & 0x10u) ? "NEAR " : "",
|
||
(flags & 0x02u) ? "E1UP " : "",
|
||
(flags & 0x08u) ? "E2UP " : "");
|
||
|
||
if (channel == BASS_CH)
|
||
{
|
||
if (s_bass_log_left > 0)
|
||
s_bass_log_left--;
|
||
}
|
||
else if (s_pitch_log_left > 0)
|
||
{
|
||
s_pitch_log_left--;
|
||
}
|
||
}
|
||
|
||
/* NoteOn 路径:策略变化时清旧音;返回映射后音高 */
|
||
static int App_Auto_MapNoteOn(int channel, int key)
|
||
{
|
||
#if CHORD_REG_FIX_EN
|
||
uint8_t fp = App_Auto_RegFingerprint();
|
||
uint8_t flags = 0;
|
||
int out;
|
||
|
||
if (fp != s_reg_fp)
|
||
{
|
||
if (s_reg_fp != 0xFFu)
|
||
{
|
||
LOG_I("REG", "fp %u->%u chord=%u deg=%d xp=%u xf=%d",
|
||
(unsigned)s_reg_fp, (unsigned)fp,
|
||
(unsigned)KEY_ID_1629,
|
||
App_Auto_ChordDegree(),
|
||
(unsigned)mGuiData[GUI_TRANSPOSE].Current,
|
||
App_Auto_XposeExtra());
|
||
App_Auto_QueueAllNotesOffMelodic();
|
||
}
|
||
else
|
||
{
|
||
LOG_I("REG", "fp init %u chord=%u deg=%d xp=%u",
|
||
(unsigned)fp, (unsigned)KEY_ID_1629,
|
||
App_Auto_ChordDegree(),
|
||
(unsigned)mGuiData[GUI_TRANSPOSE].Current);
|
||
}
|
||
s_reg_fp = fp;
|
||
s_pitch_log_left = PITCH_LOG_PER_FP;
|
||
s_bass_log_left = BASS_LOG_PER_FP;
|
||
s_ch_ever_mask = 0; /* 新策略周期重新报到通道 */
|
||
}
|
||
|
||
out = App_Auto_MapNote(channel, key, &flags);
|
||
if (channel >= 0 && channel < 16)
|
||
{
|
||
uint16_t bit = (uint16_t)(1u << channel);
|
||
if ((s_ch_ever_mask & bit) == 0u)
|
||
{
|
||
s_ch_ever_mask |= bit;
|
||
LOG_I("REG", "ch-seen ch%u role=%s key=%d",
|
||
(unsigned)channel, App_Auto_RoleName(channel), key);
|
||
}
|
||
}
|
||
App_Auto_LogPitch(channel, key, out, flags, 1);
|
||
return out;
|
||
#else
|
||
(void)channel;
|
||
return key;
|
||
#endif
|
||
}
|
||
|
||
static void Func_CallBack_ProgramChange(int channel, int program)
|
||
{
|
||
MidiFifoItem_t midimsg;
|
||
LOG_I("REG", "pc ch%u prog=%d", (unsigned)channel, program);
|
||
midimsg.msg[0] = 0xc0 | (channel & 0x0F);
|
||
midimsg.msg[1] = program;
|
||
midimsg.msg[2] = 0;
|
||
midimsg.msglen = 2;
|
||
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
|
||
}
|
||
|
||
static void Func_CallBack_NoteOff(int channel,int key)
|
||
{
|
||
MidiFifoItem_t midimsg;
|
||
uint8_t flags = 0;
|
||
int out_key = App_Auto_MapNote(channel, key, &flags);
|
||
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);
|
||
|
||
}
|
||
|
||
static void Func_CallBack_NoteOn(int channel,int key,int vel)
|
||
{
|
||
MidiFifoItem_t midimsg;
|
||
int out_key;
|
||
|
||
if (vel == 0)
|
||
{
|
||
uint8_t flags = 0;
|
||
out_key = App_Auto_MapNote(channel, key, &flags); /* 视同 NoteOff */
|
||
}
|
||
else
|
||
out_key = App_Auto_MapNoteOn(channel, key);
|
||
|
||
midimsg.msg[0] = 0x90 | (channel & 0x0F);
|
||
midimsg.msg[1] = (uint8_t)out_key;
|
||
midimsg.msg[2] = (uint8_t)vel;
|
||
midimsg.msglen = 3;
|
||
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
|
||
|
||
}
|
||
|
||
static void Func_CallBack_CC(int channel,int cc,int value)
|
||
{
|
||
if(cc == 7|| cc == 11)
|
||
{
|
||
return;
|
||
}
|
||
MidiFifoItem_t midimsg;
|
||
midimsg.msg[0] = 0xb0 | channel;
|
||
midimsg.msg[1] = cc;
|
||
midimsg.msg[2] = value;
|
||
midimsg.msglen = 3;
|
||
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
|
||
|
||
}
|
||
|
||
static void Func_CallBack_PitchWheel(int channel,int cc,int value)
|
||
{
|
||
MidiFifoItem_t midimsg;
|
||
midimsg.msg[0] = 0xe0 | channel;
|
||
midimsg.msg[1] = cc;
|
||
midimsg.msg[2] = value;
|
||
midimsg.msglen = 3;
|
||
mymidififo_InQueue(&m_MidiSendFifo,&midimsg);
|
||
}
|
||
|
||
/* Flash Map 0903:对照 ExtFlash_Tone_Addr.h / Doc/.../FLASH_MAP.txt */
|
||
static const char *ToneFlashMapBank(uint32_t abs)
|
||
{
|
||
if (abs >= EXTFLASH_BIN1_RHYTHM_ADDR &&
|
||
abs < EXTFLASH_BIN1_RHYTHM_ADDR + EXTFLASH_BIN1_RHYTHM_SIZE)
|
||
return "BIN1_RHYTHM";
|
||
if (abs >= EXTFLASH_BIN2_SONG_HAITIAN_ADDR &&
|
||
abs < EXTFLASH_BIN2_SONG_HAITIAN_ADDR + EXTFLASH_BIN2_SONG_HAITIAN_SIZE)
|
||
return "BIN2_SONG_HAITIAN";
|
||
if (abs >= EXTFLASH_BIN3_UNIVERSAL_ADDR &&
|
||
abs < EXTFLASH_BIN3_UNIVERSAL_ADDR + EXTFLASH_BIN3_UNIVERSAL_SIZE)
|
||
return "BIN3_UNIVERSAL";
|
||
if (abs >= FLASH_ADDR_AUTOBAND_LEGACY &&
|
||
abs < FLASH_ADDR_AUTOBAND_LEGACY + 0x10000UL)
|
||
return "LEGACY_AUTOBAND";
|
||
return "OUT_OF_MAP";
|
||
}
|
||
|
||
static uint32_t s_flash_rd_last_base = 0xFFFFFFFFu;
|
||
static uint16_t s_flash_rd_log_cnt = 0;
|
||
|
||
static int Func_CallBack_ReadFlash(int address,int length,uint8_t* pOutput)
|
||
{
|
||
uint32_t base = (uint32_t)ADDRESS;
|
||
uint32_t abs = base + (uint32_t)address;
|
||
const char *bank;
|
||
|
||
if (base != s_flash_rd_last_base) {
|
||
s_flash_rd_last_base = base;
|
||
s_flash_rd_log_cnt = 0;
|
||
LOG_I("FLASH_RD", "bank base=0x%08X map=%s (BIN1=0x%08X BIN2=0x%08X BIN3=0x%08X)",
|
||
(unsigned)base,
|
||
ToneFlashMapBank(base),
|
||
(unsigned)EXTFLASH_BIN1_RHYTHM_ADDR,
|
||
(unsigned)EXTFLASH_BIN2_SONG_HAITIAN_ADDR,
|
||
(unsigned)EXTFLASH_BIN3_UNIVERSAL_ADDR);
|
||
}
|
||
|
||
bank = ToneFlashMapBank(abs);
|
||
/* 加载阶段打印前若干次 + 每次 offset==0;避免播放期刷屏 */
|
||
if (s_flash_rd_log_cnt < 16u || address == 0) {
|
||
LOG_I("FLASH_RD", "abs=0x%08X off=%d len=%d map=%s",
|
||
(unsigned)abs, address, length, bank);
|
||
if (s_flash_rd_log_cnt < 0xFFFFu)
|
||
s_flash_rd_log_cnt++;
|
||
if (s_flash_rd_log_cnt == 16u)
|
||
LOG_I("FLASH_RD", "further reads omitted (same base)");
|
||
} else if (bank[0] == 'O') {
|
||
/* 读到 Map 外:始终告警 */
|
||
LOG_W("FLASH_RD", "OUT_OF_MAP abs=0x%08X off=%d len=%d base=0x%08X",
|
||
(unsigned)abs, address, length, (unsigned)base);
|
||
}
|
||
|
||
W25Q128_Read(pOutput, abs, length);
|
||
return 0;
|
||
}
|
||
//
|
||
void App_Auto_Init(void)
|
||
{
|
||
LOG_I("REG", "ch map bass=%d drum=%d fix=%d E1=%d E2=%d Gs4=%d",
|
||
BASS_CH, DRUM_CH, CHORD_REG_FIX_EN, MIDI_E1, MIDI_E2, MIDI_GSHARP4);
|
||
LOG_I("REG", "PITCH log: chN(role) in->out d=delta deg=I..VII xp=xpose xf=#Fextra fl=flags");
|
||
AutoBandTop1_SetPresetStoreBuffer(pStoreBuffer,PRESET_BUFFER_SIZE);
|
||
AutoBandTop1_Init();
|
||
AutoBandTop1_RegisterCallBack_NoteOn(Func_CallBack_NoteOn);
|
||
AutoBandTop1_RegisterCallBack_NoteOff(Func_CallBack_NoteOff);
|
||
AutoBandTop1_RegisterCallBack_ProgChange(Func_CallBack_ProgramChange);
|
||
AutoBandTop1_RegisterCallBack_CC(Func_CallBack_CC);
|
||
AutoBandTop1_RegisterCallBack_PitchWheel(Func_CallBack_PitchWheel);
|
||
AutoBandTop1_RegisterReadFlashCallback(Func_CallBack_ReadFlash);
|
||
m_presetCount = AutoBandTop1_GetPresetItemCount();
|
||
|
||
int ret = AutoBandTop1_LoadPresetItemFromFlash(0);
|
||
if (ret != 0)
|
||
{
|
||
uint8_t hdr[16];
|
||
W25Q128_Read(hdr, (uint32_t)ADDRESS, 16);
|
||
LOG_E("AUTO", "LoadPreset fail ret=%d addr=0x%08X map=%s magic=%02X%02X%02X%02X cnt=%lu",
|
||
ret, (unsigned)ADDRESS, ToneFlashMapBank((uint32_t)ADDRESS),
|
||
hdr[0], hdr[1], hdr[2], hdr[3],
|
||
(unsigned long)(hdr[12] | (hdr[13] << 8) | (hdr[14] << 16) | (hdr[15] << 24)));
|
||
}
|
||
else
|
||
{
|
||
LOG_I("AUTO", "LoadPreset ok name=%s addr=0x%08X map=%s",
|
||
AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?",
|
||
(unsigned)ADDRESS, ToneFlashMapBank((uint32_t)ADDRESS));
|
||
AutoBandTop1_ChangeBPM(mGuiData[1].Current);
|
||
}
|
||
}
|
||
|
||
//void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
|
||
//{
|
||
// if (htim == (&htim6)) {
|
||
// // 1ms timer
|
||
// AutoBandTop1_Metronome_External_1msTick();
|
||
// }
|
||
//}
|
||
|
||
// 接下来的<E69DA5>??????题是如何测试<E6B58B>??????
|
||
// <20>??????以调<E4BBA5>??????
|
||
//void AutoBandTop1_Start(void); //<2F>??????动节<E58AA8>??????
|
||
//void AutoBandTop1_Stop(void); //停<>??伴<>??
|
||
//void AutoBandTop1_SyncStart(void);//同<>?<3F>启<EFBFBD>??????
|
||
//void AutoBandTop1_SetRunVar(int varIndex);//设置变<E7BDAE>?<3F>(0-3<>??????
|
||
//void AutoBandTop1_Preamble(int index);//设置前<E7BDAE>?<3F>(0-3<>??????
|
||
//void AutoBandTop1_Postamble(int index);//位置尾<E7BDAE>?<3F>(0-3<>??????
|
||
//void AutoBandTop1_Fillin(void);//触发一<E58F91>??????插入
|
||
//如果<E5A682>??????同<>?<3F>启<EFBFBD>??????,需要再调用void AutoBandTop1_Note_On(int32_t key,int32_t vel);来输入一<E585A5>??????音<>?<3F>(例<EFBC88>?<3F>C3)来<EFBC89>??????定和弦根<E5BCA6>??????
|
||
//在哪里调用这些函数?
|
||
|