#define DEBUG_PRINT_ENABLE 0

#include <platform.h>
#include <xs1.h>
#include <stdlib.h>
#include <string.h>
#include <xcore/parallel.h>
#include <xcore/channel.h>
#include <xcore/hwtimer.h>
#include <xcore/port.h>
#include <xcore/select.h>
#include "dsp_user_impl.h"
#include "DSBuild.h"
#include "filter.h"

#include "xua_conf.h"
#include <assert.h>
#include "audio_ex3d_control.h"
#include "c_dsp.h"
#include <stdio.h>
#include "xua_hid_report.h"
#include "xc_ptr.h"
#include "dnr_dsp_buf.h"
#include "roleswitchflag.h"
#include "debug_print.h"
#include "swlock.h"

// Global flash lock defined in lfs_services.c — protects all flash hardware access
extern swlock_t flash_lock;
// 改动原因：添加user_func.h头文件，用于调用save_value和load_value函数保存/加载模式到/从flash
extern void save_value(unsigned char *path, unsigned char value);
extern unsigned char load_value(unsigned char *path);
#if EQ_EN

#include "eq_flash_storage.h"
extern unsigned g_request_eq_mode, g_new_eq_mode;
extern unsigned g_force_request_eq_mode_change, g_force_eq_mode_change;
#endif

extern void device_reboot(void);
unsigned g_ex3d_key_verified = 0;
int32_t sys_vol = 0;
unsigned g_mic_vol_cmd_pending = 0;
// 0xFA boot sync收到后置1；ex3d_task用此标志决定是否保留boot sync的阈值
static volatile int g_boot_lmt_threshold_loaded = 0;
static volatile int g_boot_footstep_expand_gain_loaded = 0;
static volatile int g_boot_angle_loaded = 0;
// 改动原因：修复“主机已通过CMD_ANGLE写入，但随后启动阶段0xF9(来自flash旧值)又覆盖回默认角度”的竞态。
// 该标志在收到任意CMD_ANGLE写入后置1；若置1，则忽略后续0xF9覆盖，确保“最后一次主机写入”为准。
static volatile int g_runtime_angle_written = 0;
// 改动原因：修复0xF9角度同步与ex3d_task初始化的竞态。
// 当0xF9在ex3d_task初始化完成后才到达时，旧逻辑只缓存不应用，导致GET CMD_ANGLE仍返回默认值。
// 通过该标志判断EX3D是否已完成初始化，若已完成则在收到0xF9时立即应用角度。
static volatile int g_ex3d_init_done = 0;
// audio_ex3d_init会覆盖Ex3dExpandGain为库默认值；用此变量保存0xFD boot sync收到的值
static volatile int32_t g_boot_footstep_expand_gain_value = 12;
static volatile int32_t g_boot_lmt_threshold_value = -5;
static volatile uint32_t g_boot_angle_values[8] = {315, 45, 0, 0, 225, 135, 270, 90};

chanend_t uc_ex3d_to_ubm, uc_eq_data;
static unsigned ubm_sample_freq = 0;

#if (STEREO_8K == 1) || (STEREO_2K == 1)
#define UBM_TO_EX3D_CHANS (2)
#define EX3D_TO_UBM_CHANS (2)
#else
#define UBM_TO_EX3D_CHANS (NUM_USB_CHAN_OUT)
#define EX3D_TO_UBM_CHANS (2)
#endif

// tile 1, mic level
unsigned g_mic_level_t1;


#if defined(IR_SWITCHING_MODE)
enum {IR_OFF=0, IR_GAME=1, IR_MUSIC=2, IR_MOVIE=3, IR_7_1_GAME=4, IR_7_1_MUSIC=5, IR_7_1_MOVIE=6};
unsigned g_3d_on_off_t1 = IR_OFF;     // Default 3D off
#else
enum {A3D_OFF=0, A3D_VON=1, A3D_ON=2};
unsigned g_3d_on_off_t1 = A3D_OFF;     // Default 3D off
#endif
#define DNR_ON 1
#define DNR_OFF 0
#define MUTE_ON 1
#define MUTE_OFF 0
unsigned g_mute_on_off_t1 = MUTE_ON;

// 数字监听 tile1 共享变量（由 tile0 通过 cc_mic_level 通道同步）
unsigned g_monitor_switch_t1 = 0;       // 耳返开关 tile1 副本（0=关闭，1=开启）

unsigned g_mute_on_off_t0 = MUTE_OFF;

unsigned g_game_mode = 0;

#if USE_EX3D == 1
static void ex3d_apply_boot_angles(void)
{
    int apply_count = (NUM_USB_CHAN_OUT < 8) ? NUM_USB_CHAN_OUT : 8;
    for (int i = 0; i < apply_count; ++i) {
        uint32_t angle = g_boot_angle_values[i];
        EX3D_VAngle[i] = (angle >> 16) & 0xFFFF;
        EX3D_HAngle[i] = angle & 0xFFFF;
        EX3DAudio_SetAngle(i + 1, EX3D_VAngle[i], EX3D_HAngle[i], (i == (apply_count - 1)));
        debug_printf("ex3d_apply_boot_angles: %d, %d\n", EX3D_VAngle[i], EX3D_HAngle[i]);
    }
}
#endif

// On UserBufferManagement Tile (1)
static unsigned frame_index = 0;
//static unsigned dnr_frame_idx = 0;
static int32_t ubm_egress[UBM_TO_EX3D_CHANS][DSP_BLOCK_LENGTH];     //leaving ubm
static int32_t ubm_ingress[EX3D_TO_UBM_CHANS][DSP_BLOCK_LENGTH];    //entering ubm
#if AIZIP_DNR == 1
static int32_t ubm_micIn[DSP_CH_NUM][DNR_DSP_FRAME_SIZE]; //leaving ubm
static int32_t ubm_micOut[DSP_CH_NUM][DNR_DSP_FRAME_SIZE]; //entering ubm
#endif
chanend_t uc_dsp_to_dnr_t1;
//chanend_t uc_dsp_to_dnr_t0;
chanend_t uc_key_to_ubm_t0;
volatile EXTERN DS_BOOL m_hThread;
extern unsigned g_dnr_init_flag;
void SetEqDataChan (chanend_t c) {
    uc_eq_data = c;
}
void key_receiver(chanend_t c)
{
    debug_printf("===> key_receiver\n");
#if DNR_ENABLE
    unsigned dnr_init_flag = 0;
    while (dnr_init_flag == 0) {
        GET_SHARED_GLOBAL(dnr_init_flag, g_dnr_init_flag);
        asm("nop");
    }

#endif
   // tile0
    // load the license key
    // This function must be called before audio_ex3d_activate_key.
    swlock_acquire(&flash_lock);
    audio_ex3d_load_key(c);
    swlock_release(&flash_lock);
    SET_SHARED_GLOBAL(g_ex3d_key_verified, 1);
}

void key_sender(chanend_t c)
{
    debug_printf("===> key_sender\n");

    // tile1
    // activate the license key
    // if the keys match, the function returns 0.
    int ret = audio_ex3d_activate_key(c);
    if (ret == NO_ERR) {
        debug_printf("License keys match.\n");
    } else {
        debug_printf("License keys do not match. Error code: %d\n", ret);
    }
}

void UserBufferManagementInit(unsigned sampFreq)
{
    ubm_sample_freq = sampFreq;
#if USE_EX3D == 1
    memset(ubm_ingress, 0, sizeof(ubm_ingress));
    memset(ubm_egress,  0, sizeof(ubm_egress));
    frame_index = 0;

    assert(uc_ex3d_to_ubm);
    while( !m_hThread ){}
    debug_printf("[UserBufferManagementInit] samfreq:%d\n\r", sampFreq);
    int ret = audio_ex3d_change_parameter(NUM_USB_CHAN_OUT, NUM_USB_CHAN_OUT * FRAME_SIZE * sizeof(AUDIO_T), sampFreq, sizeof(AUDIO_T));
    if(ret != NO_ERR) {
        debug_printf("audio_ex3d_change_parameter() error:%d\n\r", ret);
    } else {
        debug_printf("audio_ex3d_change_parameter() success\n\r");
    }
    // 改动原因：修复“上电后GET CMD_ANGLE仍返回315”的问题。
    // audio_ex3d_change_parameter 内部会把 EX3D_VAngle/EX3D_HAngle 重置为库默认值(315,45,...)，
    // 若 0xF9 已同步了 flash 中的角度（或主机期间已通过 CMD_ANGLE 改过），必须在此重新应用，
    // 否则采样率切换/流启动时刚建立的角度会被静默覆盖，导致 GET 依旧读到默认值315。
    if (g_boot_angle_loaded || g_runtime_angle_written) {
        ex3d_apply_boot_angles();
        debug_printf("UserBufferManagementInit: reapplied ex3d angles after change_parameter\n");
    }
          // ② 预热：推一个零帧穿过 ex3d，触发所有 worker 加载 + 首次同步
      chan_out_buf_word(uc_ex3d_to_ubm, (const uint32_t *)ubm_egress,
                        UBM_TO_EX3D_CHANS * DSP_BLOCK_LENGTH);
      chan_in_buf_word (uc_ex3d_to_ubm, (uint32_t *)ubm_ingress,
                        EX3D_TO_UBM_CHANS * DSP_BLOCK_LENGTH);
      memset(ubm_ingress, 0, sizeof(ubm_ingress));  // 清掉哑帧输出
#endif
}
float fLevel[NUM_USB_CHAN_OUT] = {0,};
enum {UBM_A3D_OFF=0, UBM_A3D_VON=1, UBM_A3D_ON=2};
extern void buffer_exchange(chanend_t c_data, unsigned sampsFromUsbToAudio[], unsigned sampsFromAudioToUsb[], unsigned sample_freq);
extern unsigned int is_eq_disabled(void);
extern unsigned int g_eq_enable;

void UserBufferManagement(unsigned sampsFromUsbToAudio[], unsigned sampsFromAudioToUsb[])
{
    uint32_t is_3d;
    unsigned is_monitor;

#if USE_EX3D == 1
#if defined(SPATIAL_DRAMA) || defined(SPATIAL_GAME) || defined(SPATIAL_MOVIE) || defined(SPATIAL_MUSIC)
    // 6ch version
    unsigned tmp[8]={0,0,0,0,0,0,0,0}, downmix[8]={0,0,0,0,0,0,0,0};
#endif

    GET_SHARED_GLOBAL(is_3d, g_3d_on_off_t1);
#if (HID_CONTROLS > 0)
    AUDIO_T absVal, maxVal;
#endif
    //Frame the samples going from the usb to the DAC into frames of 8
    for(int ch=0;ch<UBM_TO_EX3D_CHANS;ch++) {
        ubm_egress[ch][frame_index] = sampsFromUsbToAudio[ch];

#if (HID_CONTROLS > 0)
        maxVal = 0;
        absVal = ubm_egress[ch][frame_index];
        if(absVal < 0) absVal = -absVal;
        if(maxVal < absVal) maxVal = absVal;
#if defined(AUDIO_T_16)
        fLevel[ch] = (float)maxVal / 32768;
#else
        fLevel[ch] = (float)maxVal / 2147483648;
#endif
#endif
    }

#if defined(SPATIAL_DRAMA) || defined(SPATIAL_GAME) || defined(SPATIAL_MOVIE) || defined(SPATIAL_MUSIC) || defined(IR_SWITCHING_MODE)
        // 6ch version
    // C and LFE -3dB mixed to L and R.
    // L = L + C*-3dB + LFE*-3dB
    // R = R + C*-3dB + LFE*-3dB

    int32_t input_L = (int32_t)sampsFromUsbToAudio[0];
    int32_t input_R = (int32_t)sampsFromUsbToAudio[1];
    int32_t input_C = (int32_t)sampsFromUsbToAudio[2];
    int32_t input_LFE = (int32_t)sampsFromUsbToAudio[3];

    // -3dB Gain = 10^(-3/20) ~= 0.707946
    // Using 0.70710678 (1/sqrt(2)) approximation for -3dB
    // Q31 factor: 0.70710678 * 2^31 = 1518500250
    const int32_t gain_minus_3db = 1518500250;

    int32_t C_weighted = (int32_t)(((int64_t)input_C * gain_minus_3db) >> 31);
    int32_t LFE_weighted = (int32_t)(((int64_t)input_LFE * gain_minus_3db) >> 31);

    int64_t sum_L = (int64_t)input_L + C_weighted + LFE_weighted;
    int64_t sum_R = (int64_t)input_R + C_weighted + LFE_weighted;

    // Saturation logic
    if (sum_L > 2147483647) sum_L = 2147483647;
    else if (sum_L < -2147483648) sum_L = -2147483648;

    if (sum_R > 2147483647) sum_R = 2147483647;
    else if (sum_R < -2147483648) sum_R = -2147483648;

    // only for Stereo game, Stereo music and Stereo movie mode
    if ((is_3d > IR_OFF) && (is_3d < IR_7_1_GAME)) {
        // stereo mode
        ubm_egress[0][frame_index] = 0;
        ubm_egress[1][frame_index] = 0;
        ubm_egress[2][frame_index] = 0;
        ubm_egress[3][frame_index] = 0;
        ubm_egress[4][frame_index] = 0;
        ubm_egress[5][frame_index] = 0;
        ubm_egress[6][frame_index] = (int32_t)sum_L;    //SL
        ubm_egress[7][frame_index] = (int32_t)sum_R;    //SR
    } else {
        ubm_egress[0][frame_index] = (int32_t)sum_L;
        ubm_egress[1][frame_index] = (int32_t)sum_R;
    }

    // Zero out C (2) and LFE (3) since they are mixed into L/R
    ubm_egress[2][frame_index] = 0;
    ubm_egress[3][frame_index] = 0;
#endif

    for(int ch=0;ch<DSP_MIXER_OUTPUT_CHANNEL_COUNT;ch++) {
        sampsFromUsbToAudio[ch] = ubm_ingress[ch][frame_index];
        //sampsFromAudioToUsb[ch] = sampsFromUsbToAudio[ch]; //loopback
    }

    frame_index++;

    if(frame_index == DSP_BLOCK_LENGTH){
        // When the frame is full then send out the mul;ti-channel data to the FIR processors on tile 0 then tile 1.
        frame_index = 0;
        chan_out_buf_word(uc_ex3d_to_ubm, (const uint32_t *)ubm_egress, UBM_TO_EX3D_CHANS * DSP_BLOCK_LENGTH);
        // And get a stereo frames worth of data back from the mixer process
        chan_in_buf_word(uc_ex3d_to_ubm, (uint32_t *)ubm_ingress, EX3D_TO_UBM_CHANS * DSP_BLOCK_LENGTH);
    };
#endif
#if EQ_EN == 1 && USE_EX3D == 0
    buffer_exchange(uc_eq_data, sampsFromUsbToAudio, sampsFromAudioToUsb, ubm_sample_freq);
#elif DNR_ENABLE == 1
    buffer_exchange(uc_eq_data, sampsFromUsbToAudio, sampsFromAudioToUsb, ubm_sample_freq);
#endif
    GET_SHARED_GLOBAL(is_monitor, g_monitor_switch_t1);
    if (is_monitor) {
        sampsFromUsbToAudio[0] += (unsigned)((((int)sampsFromAudioToUsb[0]) >> 1));
        sampsFromUsbToAudio[1] += (unsigned)((((int)sampsFromAudioToUsb[1]) >> 1));
    }
}


#ifndef HID_MAX_DATA_BYTES
#define HID_MAX_DATA_BYTES ( 64 )
#endif
unsigned hidSendData[HID_MAX_DATA_BYTES / 4];

#if (HID_CONTROLS == 1)

#include "user_hid.h"
static unsigned char lastHidData;
void hid_receive_task_in_c(unsigned char * RcvData, unsigned * SendData);
typedef enum {
	CMD_CH_NUM = 0x80,
	CMD_OPEN = 0x81,
	CMD_ONOFF = 0x82,
	CMD_ANGLE = 0x83,
	CMD_SOUND_FIELD = 0x84,
	CMD_MUTE = 0x85,
	CMD_ON_GAIN = 0x86,
	CMD_LMT_THRESHOLD = 0x87,
	CMD_UPMIX = 0x88,
	CMD_LFE = 0x89,
	CMD_OFF_GAIN = 0x8A,
	CMD_SOUND_FIELD_NUM = 0x8B,
	CMD_SOUND_FIELD_NAME = 0x8C,
	CMD_LEVEL = 0x8D,
	CMD_LMT_ATTACKK_TIME = 0x8E,
	CMD_LMT_RELEASE_TIME = 0x8F,
	CMD_TEST_CANCEL = 0x90,
	CMD_TEST_STEP = 0x91,
	CMD_TEST_ROTATE = 0x92,
	CMD_EXPAND_GAIN = 0x93, // dhlee++
	CMD_REDUCE_GAIN = 0x94, // dhlee++
} PlbkCmdCode;

#define CMD_SET(item)                  ((item) & ~0x0100)
#define CMD_GET(item)                  ((item) |  0x0100)
#define CMD_PARAM(item)                ((item) |  0x0200)
#define CMD_CLR_PARAM(item)            ((item) & ~0x0200)
#define CMD_FWD(item)                  ((item) |  0x0400)
#define CMD_FWD_MASK(item)             ((item) &  0x0400)
#define CMD_CLR_FWD(item)              ((item) & ~0x0400)
#define CMD_FWD_TO_2ND_DEV(item)       ((item) |  0x0800)
#define CMD_FWD_TO_2ND_DEV_MASK(item)  ((item) &  0x0800)
#define CMD_CLR_FWD_TO_2ND_DEV(item)   ((item) & ~0x0800)
#define CMD_MASK                       (~(CMD_SET(0)|CMD_GET(0)|CMD_PARAM(0)|CMD_FWD(0)|CMD_FWD_TO_2ND_DEV(0)))
#define CMD_ITEM(cmd)                  ((cmd) & CMD_MASK)

void UserHIDInit(void) {
    return;
}

size_t UserHIDGetData( const unsigned id, unsigned char hidData[HID_MAX_DATA_BYTES]) {
    size_t Length = 0;

    if(id == 1) {
            unsigned char *reportData = (unsigned char *)hidSendData;
            for (unsigned char i=0; i<HID_MAX_DATA_BYTES; i++) {
                hidData[i] = reportData[i];
            }
        Length = HID_MAX_DATA_BYTES;
    }

    return Length;
}
void hid_receive_task_in_c(unsigned char * RcvData, unsigned * SendData)
{
    unsigned command_code, cmdItem, idx = 1;
    unsigned *pRcvBuf = (unsigned *)(RcvData + 4);
    unsigned *pSendBuf = SendData;
    unsigned angle;
    // 改动原因：
    // - host UI 根据 EX3D_SF_NUM 遍历声场列表；现在 EX3D_SF_NUM=3，只暴露按键支持的3个“带算法声场”(GAME/MUSIC/MOVIE)；
    // - 算法开关通过 CMD_ONOFF 单独控制，CMD_SOUND_FIELD 只选择声场，不允许隐式打开算法；
    // 因此在HID命令层仅缓存“最近一次声场索引(0~2)”，供 CMD_SOUND_FIELD / CMD_ONOFF 共用（算法OFF时也能记住选择）。
    static unsigned last_sf_idx_0_2 = 0;      // 0~2: GAME/MUSIC/MOVIE

    command_code = pRcvBuf[0];
    cmdItem = CMD_ITEM(command_code);

    SendData[idx++] = command_code;
    switch (cmdItem) {
#if USE_EX3D == 1
    case CMD_OPEN:
        debug_printf("CMD_OPEN\r\n");
        break;

    case CMD_ONOFF:
        if (CMD_SET(CMD_ONOFF) == command_code) {
            // set DrAX on/off
            //audio_ex3d_onoff(pRcvBuf[1]);
            uint32_t onoff = pRcvBuf[1];
            //audio_ex3d_set_onoff(pRcvBuf[1]);
            audio_ex3d_set_onoff(onoff);
            uint32_t cur_mode;
            GET_SHARED_GLOBAL(cur_mode, g_3d_on_off_t1);
            if (onoff == 0) {
                // 改动原因：关闭算法时记录当前声场索引(0~2)，供下次打开时恢复；然后仅把共享变量置为IR_OFF
                if (cur_mode == IR_7_1_MOVIE) {
                    last_sf_idx_0_2 = 2;
                } else if (cur_mode == IR_7_1_GAME) {
                    last_sf_idx_0_2 = 1;
                } else {
                    last_sf_idx_0_2 = 0;
                }
                cur_mode = IR_OFF;
                SET_SHARED_GLOBAL(g_3d_on_off_t1, cur_mode);
            } else {
                // 改动原因：打开算法时恢复到最近一次选择的声场(0~2)，并统一映射到 IR_GAME..IR_MOVIE（不再暴露6个族）
                unsigned new_mode;
                if (last_sf_idx_0_2 == 2) new_mode = IR_7_1_MOVIE;
                else if (last_sf_idx_0_2 == 1) new_mode = IR_7_1_GAME;
                else new_mode = IR_GAME;
                SET_SHARED_GLOBAL(g_3d_on_off_t1, new_mode);
            }
            debug_printf("Set CMD_ONOFF : %u\r\n", pRcvBuf[1]);
        } else {
            debug_printf("Get CMD_ONOFF : %u\r\n", bEX3D_On);
            pSendBuf[idx] = bEX3D_On;
        }
        break;

    case CMD_CH_NUM:
        if (CMD_GET(CMD_CH_NUM) == command_code) {
            debug_printf("Get CMD_CH_NUM : %u\r\n", NUM_USB_CHAN_OUT);
            pSendBuf[idx] = NUM_USB_CHAN_OUT;
        }
        break;

    case CMD_UPMIX:
        if (CMD_SET(CMD_UPMIX) == command_code) {
            debug_printf("Set CMD_UPMIX : %u\r\n", pRcvBuf[1]);
            audio_ex3d_set_upmix(pRcvBuf[1]);
        } else {
            debug_printf("Get CMD_UPMIX : %u\r\n", bEX3D_Upmix);
            pSendBuf[idx] = bEX3D_Upmix;
        }
        break;

    case CMD_ANGLE:
        if (CMD_SET(CMD_ANGLE) == command_code) {
            uint32_t ChNum = pRcvBuf[1];
            if (ChNum < NUM_USB_CHAN_OUT) {
                angle = pRcvBuf[2];
                EX3D_VAngle[ChNum] = (angle >> 16) % 181;
                EX3D_HAngle[ChNum] = (angle & 0xffff) % 360;
                EX3DAudio_SetAngle(ChNum+1, EX3D_VAngle[ChNum], EX3D_HAngle[ChNum], true);
                // 改动原因：记录“运行期角度已被主机改写”，并同步缓存，防止后续0xF9旧值覆盖。
                g_boot_angle_values[ChNum] = ((uint32_t)EX3D_VAngle[ChNum] << 16) | (uint32_t)EX3D_HAngle[ChNum];
                g_boot_angle_loaded = 1;
                g_runtime_angle_written = 1;
                debug_printf("[%s] Set angle ch%d-%d,%d\n",__FUNCTION__, ChNum+1, EX3D_VAngle[ChNum], EX3D_HAngle[ChNum]);
            } else {
                uint8_t bApply = false;
                debug_printf("[%s] Set angle(V,H):",__FUNCTION__);
                ChNum = NUM_USB_CHAN_OUT;
                for(int i=0; i<ChNum; i++) {
                    angle = pRcvBuf[2 + i];
                    EX3D_VAngle[i] = (angle >> 16) % 181;
                    EX3D_HAngle[i] = (angle & 0xffff) % 360;
                    if(i == (ChNum - 1)) bApply = true;
                    EX3DAudio_SetAngle(i+1, EX3D_VAngle[i], EX3D_HAngle[i], bApply);
                    // 改动原因：批量写入时同样更新本地启动角度缓存，避免0xF9旧值回灌。
                    g_boot_angle_values[i] = ((uint32_t)EX3D_VAngle[i] << 16) | (uint32_t)EX3D_HAngle[i];
                    debug_printf(" ch%d-%d,%d,", i+1, EX3D_VAngle[i], EX3D_HAngle[i]);
                }
                // 改动原因：批量写入成功后置位，后续0xF9不再覆盖主机刚写的角度。
                g_boot_angle_loaded = 1;
                g_runtime_angle_written = 1;
                debug_printf("\n");
            }
            debug_printf("Set CMD_ANGLE : %u\r\n", pRcvBuf[1]);
        } else {
            debug_printf("Get CMD_ANGLE : %u\r\n", pRcvBuf[1]);

            uint32_t ChNum = pRcvBuf[1];
            if (ChNum < NUM_USB_CHAN_OUT) {
                angle = EX3D_VAngle[ChNum] << 16;
                angle |= EX3D_HAngle[ChNum];
                pSendBuf[idx] = angle;
                debug_printf("Get CMD_ANGLE : %u, %d, %d\n", ChNum, EX3D_VAngle[ChNum], EX3D_HAngle[ChNum]);
            } else {
                ChNum = NUM_USB_CHAN_OUT;
                for (int i = 0; i < ChNum; i++) {
                    angle = EX3D_VAngle[i] << 16;
                    angle |= EX3D_HAngle[i];
                    debug_printf("%08x, ", angle);
                    pSendBuf[idx++] = angle;
                }
            }
            debug_printf("\r\n");
        }
        break;

    case CMD_MUTE:
        if (CMD_SET(CMD_MUTE) == command_code) {
            uint32_t ChNum = pRcvBuf[1];
            if (ChNum < NUM_USB_CHAN_OUT) {
                EX3D_Mute[ChNum] = pRcvBuf[2];
                EX3DAudio_SetMute(ChNum+1, EX3D_Mute[ChNum]);
                debug_printf("[%s] Set mute ch%d-%d,%d\n",__FUNCTION__, ChNum+1, EX3D_Mute[ChNum]);
            } else {
                ChNum = NUM_USB_CHAN_OUT;
                debug_printf("[%s] Set mute:",__FUNCTION__);
                for(int i=0; i<ChNum; i++) {
                    EX3D_Mute[i] = pRcvBuf[2 + i];
                    EX3DAudio_SetMute(i+1, EX3D_Mute[i]);
                    debug_printf(" ch%d-%d,", i+1, EX3D_Mute[i]);
                }
                debug_printf("\n");
            }
            debug_printf("Set CMD_MUTE : %u\r\n", pRcvBuf[1]);
        } else {
            debug_printf("Get CMD_MUTE : %u\r\n", pRcvBuf[1]);

            uint32_t ChNum = pRcvBuf[1];
            if (ChNum < NUM_USB_CHAN_OUT) {
                pSendBuf[idx] = EX3D_Mute[ChNum];
            } else {
                ChNum = NUM_USB_CHAN_OUT;
                for (int i = 0; i < ChNum; i++) {
                    pSendBuf[idx++] = EX3D_Mute[i];
                }
            }
            debug_printf("\r\n");
        }
        break;

    case CMD_SOUND_FIELD_NUM:
        if (CMD_GET(CMD_SOUND_FIELD_NUM) == command_code) {
            debug_printf("Get CMD_SOUND_FIELD_NUM\r\n");
            pSendBuf[idx] = EX3D_SF_NUM;
        }
        break;

    case CMD_SOUND_FIELD_NAME:
        if (CMD_GET(CMD_SOUND_FIELD_NAME) == command_code) {
            int32_t sfIdx = pRcvBuf[1];
            debug_printf("Get CMD_SOUND_FIELD_NAME : ");
            if (sfIdx < EX3D_SF_NUM) {
#if defined(SPATIAL_VIDEO)
                const char * sfIdxName = "SPATIAL VIDEO"; //sfDispName[sfIdx];
#elif defined(SPATIAL_MUSIC)
                const char * sfIdxName = "SPATIAL MUSIC"; //sfDispName[sfIdx];
#elif defined(SPATIAL_GAME)
                const char * sfIdxName = "SPATIAL GAME"; //sfDispName[sfIdx];
#elif defined(SPATIAL_MOVIE)
                const char * sfIdxName = "SPATIAL MOVIE"; //sfDispName[sfIdx];
#elif defined(IR_SWITCHING_MODE)
                // 改动原因：host UI 现在只遍历 EX3D_SF_NUM=3 的声场；这里仅返回按键支持的3个“带算法声场”名字
                // mapping: sfIdx(0..2) -> IR_GAME/IR_7_1_GAME/IR_7_1_MOVIE
                const char * sfIdxName = "";
                switch (sfIdx) {
                    case 0: sfIdxName = "STEREO GAME";  break;
                    case 1: sfIdxName = "SPATIAL GAME"; break;
                    case 2: sfIdxName = "SPATIAL MOVIE"; break;
                    default: break;
                }
//                if (cur_mode == IR_GAME) {
//                    sfIdxName = spatial_game;
//                } else {
//                    if (cur_mode == IR_MUSIC) {
//                        sfIdxName = spatial_music;
//                    } else {
//                        sfIdxName = spatial_movie;
//                    }
//                }
#else
                const char * sfIdxName = "EXTREME";
#endif
                int32_t NameLen = strlen(sfIdxName);
                pSendBuf[idx++] = NameLen;
                memcpy(&pSendBuf[idx], sfIdxName, NameLen);
                //debug_printf("%s\r\n", sfDispName[sfIdx]);
            } else
                pSendBuf[idx] = 0xFFFFFFFF;
        }
        break;

    case CMD_SOUND_FIELD:
        if (CMD_SET(CMD_SOUND_FIELD) == command_code) {
            int32_t sfIdx = pRcvBuf[1];
            debug_printf("Set CMD_SOUND_FIELD : ");
            // 改动原因：
            // - host UI 现在只暴露3个“带算法声场”（GAME/MUSIC/MOVIE 三选一）。
            // - 算法开关通过 CMD_ONOFF/其它接口单独控制；CMD_SOUND_FIELD 不能隐式把算法打开/关闭。
            // 因此：CMD_SOUND_FIELD 只接受 0~2（对应 GAME/MUSIC/MOVIE），并且在算法OFF(IR_OFF)时只缓存选择、不改共享变量。
            if (sfIdx >= 0 && sfIdx < 3) {
                unsigned cur_mode;
                GET_SHARED_GLOBAL(cur_mode, g_3d_on_off_t1);

                last_sf_idx_0_2 = (unsigned)sfIdx;

                if (cur_mode == IR_OFF) {
                    // 改动原因：算法已关闭时不写 g_3d_on_off_t1，避免 event_polling 误判“主机改变声场”而自动打开算法
                    debug_printf("algorithm OFF, cache sound field idx=%d only (no implicit on/off)\n", sfIdx);
                } else {
                    // 改动原因：算法已开启时才更新声场；因EX3D_SF_NUM=3不再区分6个族
                    unsigned new_mode;
                    if (sfIdx == 1) new_mode = IR_7_1_GAME;
                    else if (sfIdx == 2) new_mode = IR_7_1_MOVIE;
                    else new_mode = IR_GAME;
                    SET_SHARED_GLOBAL(g_3d_on_off_t1, new_mode);
                    debug_printf("set sound field idx=%d -> g_3d_on_off_t1=%d (no implicit on/off)\n", sfIdx, new_mode);
                }
            } else {
                // 改动原因：EX3D_SF_NUM=3后只支持0~2，超过范围返回错误
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            // 改动原因：返回当前声场索引(0~2: GAME/MUSIC/MOVIE)；若算法OFF则返回缓存值，避免依赖 g_3d_on_off_t1=IR_OFF
            unsigned cur_mode;
            GET_SHARED_GLOBAL(cur_mode, g_3d_on_off_t1);
            if (cur_mode == IR_OFF) {
                pSendBuf[idx] = last_sf_idx_0_2;
            } else if (cur_mode == IR_7_1_MOVIE) {
                pSendBuf[idx] = 2;
            } else if (cur_mode == IR_7_1_GAME) {
                pSendBuf[idx] = 1;
            } else {
                pSendBuf[idx] = 0;
            }
        }
        break;

    case CMD_LEVEL:
        {
#if 1   //TODO LISTS
            uint32_t ChNum = pRcvBuf[1];
            uint32_t *pLevel = (uint32_t *)fLevel;

            if(ChNum > NUM_USB_CHAN_OUT) ChNum = NUM_USB_CHAN_OUT;

            // debug_printf("Get CMD_LEVEL : %d, %d\r\n", ChNum, NUM_USB_CHAN_OUT);
            for (int i = 0; i < ChNum; i++) {
                pSendBuf[idx++] = pLevel[i];
                // debug_printf("%08x, ", pLevel[i] );
            }
            // debug_printf("\r\n");
#endif
        }
        break;

    case CMD_ON_GAIN:
        if (CMD_SET(CMD_ON_GAIN) == command_code) {
            int32_t idBGain;
            memcpy(&idBGain, &pRcvBuf[1], 4);
            debug_printf("Set CMD_ON_GAIN : %d\r\n", idBGain);
            if((-100 <= idBGain) && (idBGain <= 0)) {
                Ex3dOnGain = idBGain;
                EX3DAudio_SetOnGain(Ex3dOnGain);
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            debug_printf("Get CMD_ON_GAIN : %d\r\n", Ex3dOnGain);
            memcpy(&pSendBuf[idx], &Ex3dOnGain, 4);
        }
        break;

    case CMD_OFF_GAIN:
        if (CMD_SET(CMD_OFF_GAIN) == command_code) {
            int32_t idBGain;
            memcpy(&idBGain, &pRcvBuf[1], 4);
            debug_printf("Set CMD_OFF_GAIN : %d\r\n", idBGain);
            if((-100 <= idBGain) && (idBGain <= 0)) {
                Ex3dOffGain = idBGain;
                EX3DAudio_SetOffGain(Ex3dOffGain);
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            debug_printf("Get CMD_OFF_GAIN : %d\r\n", Ex3dOffGain);
            memcpy(&pSendBuf[idx], &Ex3dOffGain, 4);
        }
        break;

    case CMD_LFE:
        if (CMD_SET(CMD_LFE) == command_code) {
            int32_t idBGain;
            memcpy(&idBGain, &pRcvBuf[1], 4);
            debug_printf("Set CMD_LFE : %d\r\n", idBGain);
            if((-100 <= idBGain) && (idBGain <= 0)) {
                Ex3dLfeGain = idBGain;
                EX3DAudio_SetLFE(true, Ex3dLfeGain);
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            debug_printf("Get CMD_LFE : %d\r\n", Ex3dLfeGain);
            memcpy(&pSendBuf[idx], &Ex3dLfeGain, 4);
        }
        break;

    case CMD_LMT_THRESHOLD:
        if (CMD_SET(CMD_LMT_THRESHOLD) == command_code) {
            int32_t threshold;
            memcpy(&threshold, &pRcvBuf[1], 4);
            debug_printf("Set CMD_LMT_THRESHOLD : %d\r\n", threshold);
            if((-35 <= threshold) && (threshold <= 0)) {
                Ex3dLimiterThreshold = threshold;
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }

            threshold = Ex3dLimiterThreshold + (sys_vol + Ex3dOnGain + Ex3dExpandGain);
            if (threshold > 0) threshold = 0;
            if (threshold < -35) threshold = -35;
            EX3DAudio_SetLimiterThreshold(threshold);

        } else {  // Get
            debug_printf("Get CMD_LMT_THRESHOLD : %d\r\n", Ex3dLimiterThreshold);
            memcpy(&pSendBuf[idx], &Ex3dLimiterThreshold, 4);
        }
        break;

    case CMD_LMT_ATTACKK_TIME:
        if (CMD_SET(CMD_LMT_ATTACKK_TIME) == command_code) {
            unsigned AttackTime;
            memcpy(&AttackTime, &pRcvBuf[1], 4);
            debug_printf("Set CMD_LMT_ATTACKK_TIME : %d\r\n", AttackTime);
            if(AttackTime <= 10) {
                Ex3dLimiterAttackTime = AttackTime;
                EX3DAudio_SetLimiterAttackTime(Ex3dLimiterAttackTime);
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            debug_printf("Get CMD_LMT_ATTACKK_TIME : %d\r\n", Ex3dLimiterAttackTime);
            memcpy(&pSendBuf[idx], &Ex3dLimiterAttackTime, 4);
        }
        break;

    case CMD_LMT_RELEASE_TIME:
        if (CMD_SET(CMD_LMT_RELEASE_TIME) == command_code) {
            unsigned ReleaseTime;
            memcpy(&ReleaseTime, &pRcvBuf[1], 4);
            debug_printf("Set CMD_LMT_RELEASE_TIME : %d\r\n", ReleaseTime);
            if(ReleaseTime <= 100) {
                Ex3dLimiterReleaseTime = ReleaseTime;
                EX3DAudio_SetLimiterReleaseTime(Ex3dLimiterReleaseTime);
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            debug_printf("Get CMD_LMT_RELEASE_TIME : %d\r\n", Ex3dLimiterReleaseTime);
            memcpy(&pSendBuf[idx], &Ex3dLimiterReleaseTime, 4);
        }
        break;

    // dhlee++

      case CMD_EXPAND_GAIN:
        if (CMD_SET(CMD_EXPAND_GAIN) == command_code) {
            int32_t idBGain;
            memcpy(&idBGain, &pRcvBuf[1], 4);
            debug_printf("Set CMD_EXPAND_GAIN : %d\r\n", idBGain);
            if((0 <= idBGain) && (idBGain <= 20)) {
                Ex3dExpandGain = idBGain;
                EX3DAudio_SetExpandGain(Ex3dExpandGain);
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            debug_printf("Get CMD_EXPAND_GAIN : %d\r\n", Ex3dExpandGain);
            memcpy(&pSendBuf[idx], &Ex3dExpandGain, 4);
        }
        break;

    case CMD_REDUCE_GAIN:
        if (CMD_SET(CMD_REDUCE_GAIN) == command_code) {
            int32_t idBGain;
            memcpy(&idBGain, &pRcvBuf[1], 4);
            debug_printf("Set CMD_REDUCE_GAIN : %d\r\n", idBGain);
            if((-20 <= idBGain) && (idBGain <= 0)) {
                Ex3dReduceGain = idBGain;
                EX3DAudio_SetReduceGain(Ex3dReduceGain);
            } else {
                pSendBuf[idx] = 0xFFFFFFFF;
            }
        } else {  // Get
            debug_printf("Get CMD_REDUCE_GAIN : %d\r\n", Ex3dReduceGain);
            memcpy(&pSendBuf[idx], &Ex3dReduceGain, 4);
        }
        break;


    default:
        break;
#endif
    }
}

void LevelMeter(AUDIO_T AudioData[NUM_USB_CHAN_OUT][FRAME_SIZE])
{
    unsigned ch;
    AUDIO_T absVal, maxVal;
  #if defined(DETECT_CHANNEL)
    AUDIO_T DetectAudioData;
    unsigned MaskAudioData = 0;
    unsigned Mask = 0x01;
    static unsigned Mode = 0;
  #endif

    for(ch=0; ch<NUM_USB_CHAN_OUT; ch++) {
        maxVal = 0;
  #if defined(DETECT_CHANNEL)
        DetectAudioData = 0;
  #endif

        for(unsigned i=0; i<FRAME_SIZE; i++) {
            absVal = AudioData[ch][i];
  #if defined(DETECT_CHANNEL)
            DetectAudioData |= absVal;
  #endif

            if(absVal < 0) absVal = -absVal;
            if(maxVal < absVal) maxVal = absVal;
        }

  #if defined(DETECT_CHANNEL)
        if(DetectAudioData != 0) MaskAudioData |= Mask;
        Mask <<= 1;
  #endif

    #if defined(AUDIO_T_16)
        fLevel[ch] = (float)maxVal / 32768;
    #else
        fLevel[ch] = (float)maxVal / 2147483648;
    #endif
    }

  #if defined(DETECT_CHANNEL)
    if(g_MaskAudioData == MaskAudioData) {
        if(g_MaintainAudioDataCnt < g_sampFreq) {
            g_MaintainAudioDataCnt += FRAME_SIZE;
        } else if(g_MaintainAudioDataCnt < (g_sampFreq + FRAME_SIZE)){
            // 1초 이상 변경 없음

            g_MaintainAudioDataCnt += FRAME_SIZE;

            if(g_MaskAudioData == 0x03) {       // 2ch only
                if(Mode == 0) {
                    Mode = 1;
                    audio_ex3d_set_angle(270, 90);
                    debug_printf("Set 2ch Angle\n\r");
                }
            } else {
                if((g_MaskAudioData != 0) && (Mode == 1)) { // 모두 0 data 인 경우는 변경하지 않음
                    Mode = 0;
                    audio_ex3d_set_angle(315, 45);
                    debug_printf("Set multi-channel Angle\n\r");
                }
            }
        } else {
            // do nothing
        }
    } else {
        g_MaintainAudioDataCnt = 0;

        g_MaskAudioData = MaskAudioData;
        debug_printf("g_MaskAudioData:%x\n\r", g_MaskAudioData);
    }
  #endif
}

extern unsigned g_ButtonVal;

void update_button(unsigned char b)
{
#if HID_CONTROLS
    SET_SHARED_GLOBAL(g_ButtonVal, 1 << b);
    hidSetChangePending(0x2);
#endif
}


#endif

#if defined (PHATEN_GSV2)
#define KEY_POLLING_INTERVAL 10000000L       // 100ms polling interval
#define HOLD_THRESHOLD 20 // time = HOLD_THRESHOLD * KEY_POLLING_INTERVAL = 2000ms
// 改动原因：添加LED闪烁相关宏定义，用于长按时直接循环闪烁3次
#define BLINK_COUNT         3  // LED闪烁次数：3次
#endif



extern uint8_t mic_vol;
//Tile 1
void hid_button_task(chanend_t cc_mic_level, chanend_t c_hidRcvData, chanend_t c_hidSendData, chanend_t c_uac_vol, chanend_t c_ex3d_hid_cmd)
{
#if defined(IR_SWITCHING_MODE)
    static uint32_t is_3d_on = IR_OFF;
#else
    static uint32_t is_3d_on = A3D_OFF;
#endif
    static uint32_t is_dnr_on = DNR_OFF;
    uint8_t tmp = 0;

    // 当前模式值（从 audiohw.xc button_task 通过 cc_mic_level 接收）
    // 0=NO_EFFECT, 1=STEREO_2K, 2=SPATIAL_GAME, 3=SPATIAL_MOVIE
    static unsigned current_mode_local = 1;

    hwtimer_t timer = hwtimer_alloc();
    static unsigned mic_level = DEFAULT_MIC_LEVEL;
    unsigned char RcvData[64] = {0};
    unsigned SendData[HID_MAX_DATA_BYTES / 4];

    SendData[0] = 1;
    for (unsigned char i=1; i<(HID_MAX_DATA_BYTES / 4); i++) SendData[i] = 0;

    current_mode_local = 1;
    debug_printf("hid_button_task init: waiting for audio_mode from button_task\n");

    SET_SHARED_GLOBAL(g_mic_level_t1, mic_level);

    hwtimer_set_trigger_time(timer, hwtimer_get_time(timer) + KEY_POLLING_INTERVAL);
    while (1) {
        SELECT_RES(
            CASE_THEN(cc_mic_level, event_mic_vol)
            , CASE_THEN(c_hidRcvData, event_hid)
            , CASE_THEN(c_ex3d_hid_cmd, event_ex3d_hid)
            , CASE_THEN(timer, event_polling)
            , CASE_THEN(c_uac_vol, event_uac_vol)
        )
        {
        event_mic_vol:
        {
            tmp = chan_in_word(cc_mic_level);
            debug_printf("event_mic_vol received: 0x%02X\n", tmp);

            if (tmp == 0xFC) {
                // 音频模式命令：来自 audiohw.xc button_task 的模式切换
                // 必须先读取payload，无论是否启用EX3D
                current_mode_local = chan_in_word(cc_mic_level);
                if (current_mode_local > 3) current_mode_local = 0;
                debug_printf("hid_button_task received audio_mode: %d\n", current_mode_local);
#if USE_EX3D == 1
                if ((current_mode_local == 0) || (current_mode_local == 1)) {
                    // 无音效模式：关闭EX3D和EQ算法
                    audio_ex3d_set_onoff(0);
                    is_3d_on = IR_OFF;
                    debug_printf("Mode 0: EX3D+EQ OFF\n");
                } else if (current_mode_local == 2) {
                    // 音乐模式：EX3D关闭，EQ开启
#if (MODE_F3_F4_FPS_UAC2 == 1)
                    audio_ex3d_set_onoff(1);
                    is_3d_on = IR_GAME;
#endif
#if (MODE_F6_F7_FPS_UAC1 == 1)
                    audio_ex3d_set_onoff(0);
                    is_3d_on = IR_OFF;
#endif
                } else if (current_mode_local == 3) {
#if (MODE_F3_F4_FPS_UAC2 == 1)
                    audio_ex3d_set_onoff(1);
                    is_3d_on = IR_7_1_GAME;
#endif
#if (MODE_F6_F7_FPS_UAC1 == 1)
                    audio_ex3d_set_onoff(1);
                    is_3d_on = IR_GAME;
#endif
                }
#endif

            } else if (tmp == 0xFD) {
                // 脚步增强按键发来的 expand gain 命令
                // 必须先读取payload，无论是否启用EX3D
                int32_t gain = (int32_t)chan_in_word(cc_mic_level);
#if USE_EX3D == 1
                if ((0 <= gain) && (gain <= 20)) {
                    Ex3dExpandGain = gain;
                    EX3DAudio_SetExpandGain(Ex3dExpandGain);
                    g_boot_footstep_expand_gain_value = gain;
                    g_boot_footstep_expand_gain_loaded = 1;
                    debug_printf("Button set CMD_EXPAND_GAIN: %d\n", gain);
                }
#endif
            } else if (tmp == 0xFA) {
                // 开机同步枪声阈值：从tile0 flash加载的Ex3dLimiterThreshold
                // 必须先读取payload，无论是否启用EX3D
                int32_t threshold = (int32_t)chan_in_word(cc_mic_level);
#if USE_EX3D == 1
                if ((-35 <= threshold) && (threshold <= 0)) {
                    Ex3dLimiterThreshold = threshold;
                    g_boot_lmt_threshold_loaded = 1;
                    g_boot_lmt_threshold_value = threshold;
                    EX3DAudio_SetLimiterThreshold(threshold);
                    // audio_ex3d_init尚未运行（T≈200ms），ex3d_task将在T=500ms后用正确的OnGain重新应用
                    debug_printf("Boot sync lmt_threshold=%d stored, will apply after ex3d init\n", threshold);
                }
#endif
            } else if (tmp == 0xFE) {
                // 数字监听开关同步命令：来自 tile0（所有模式都需要处理）
                unsigned mon_sw = chan_in_word(cc_mic_level);
                SET_SHARED_GLOBAL(g_monitor_switch_t1, mon_sw);
                debug_printf("Monitor sync: sw=%d\n", mon_sw);
            } else if (tmp == 0xF9) {
                uint32_t incoming_angles[8];
                for (int i = 0; i < 8; ++i) {
                    incoming_angles[i] = chan_in_word(cc_mic_level);
                }

#if USE_EX3D == 1
                if (g_runtime_angle_written) {
                    // 改动原因：主机运行期已写入角度时，0xF9可能携带启动早期flash旧值；此处只消费通道数据但不覆盖当前角度。
                    debug_printf("Boot sync ex3d angles ignored (runtime angle already written)\n");
                } else {
                    for (int i = 0; i < 8; ++i) {
                        g_boot_angle_values[i] = incoming_angles[i];
                    }
                    g_boot_angle_loaded = 1;
                    // 改动原因：若EX3D已初始化完成，0xF9角度同步需要立刻生效，
                    // 否则仅缓存会导致HID GET仍读到初始化时的默认角度。
                    if (g_ex3d_init_done) {
                        ex3d_apply_boot_angles();
                        debug_printf("Boot sync ex3d angles applied immediately (post-init)\n");
                    }
                    debug_printf("Boot sync ex3d angles loaded\n");
                }
#endif
            }
        }
        continue;

        event_hid:
        {
            unsigned length = chan_in_byte(c_hidRcvData);
            for(int i=0; i<length; i++) {
                RcvData[i] = chan_in_byte(c_hidRcvData);
            }
            if(RcvData[0] == 0x01) { // REPORT ID 1
#if (HID_CONTROLS == 1)
                // 0xB0/0xB1命令由process_send_params通过c_ex3d_hid_cmd直接发到tile1处理
                // 这里只需跳过，不再通过c_hidSendData回传结果
                if (length >= 3 && RcvData[1] == 0x77 &&
                    (RcvData[2] == 0xB0 || RcvData[2] == 0xB1)) {
                    // 丢弃，由event_ex3d_hid处理
                } else {
                    hid_receive_task_in_c(RcvData, SendData);
                    for(int i=0; i<(HID_MAX_DATA_BYTES / 4); i++) {
                        chan_out_word(c_hidSendData, SendData[i]);
                    }
                }
#endif
            }
        }
        continue;
        event_ex3d_hid:
        {
#if (HID_CONTROLS == 1)
            // 收到process_send_params发来的0xB0/0xB1 EX3D命令
            // 协议: cmd(1 byte) + ex3d_cmd_code(1 word) + params_len(1 byte) + params(N bytes)
            uint8_t hid_cmd = chan_in_byte(c_ex3d_hid_cmd);
            uint32_t ex3d_cmd_code = chan_in_word(c_ex3d_hid_cmd);
            uint8_t params_len = chan_in_byte(c_ex3d_hid_cmd);

            // 构建EX3D原生格式缓冲区: [0x01, 0, 0, 0, cmd_code[4], params...]
            unsigned char ex3d_rcv[64];
            memset(ex3d_rcv, 0, 64);
            ex3d_rcv[0] = 0x01;
            ex3d_rcv[4] = (uint8_t)(ex3d_cmd_code & 0xFF);
            ex3d_rcv[5] = (uint8_t)((ex3d_cmd_code >> 8) & 0xFF);
            ex3d_rcv[6] = (uint8_t)((ex3d_cmd_code >> 16) & 0xFF);
            ex3d_rcv[7] = (uint8_t)((ex3d_cmd_code >> 24) & 0xFF);
            for (int i = 0; i < params_len && i < 56; i++) {
                ex3d_rcv[8 + i] = chan_in_byte(c_ex3d_hid_cmd);
            }

            // 在tile1执行EX3D命令
            hid_receive_task_in_c(ex3d_rcv, SendData);

            // 将结果回传给process_send_params（16个word）
            for (int i = 0; i < (HID_MAX_DATA_BYTES / 4); i++) {
                chan_out_word(c_ex3d_hid_cmd, SendData[i]);
            }
#endif
        }
        continue;

        event_uac_vol:
        {
            sys_vol = chan_in_word(c_uac_vol);
#if USE_EX3D == 1
            sys_vol = sys_vol * (-1);
            int32_t threshold = Ex3dLimiterThreshold + (sys_vol + Ex3dOnGain + Ex3dExpandGain);
            if (threshold > 0) threshold = 0;
            if (threshold < -35) threshold = -35;
            EX3DAudio_SetLimiterThreshold(threshold);
            debug_printf("Set CMD_LMT_THRESHOLD : %d\r\n", threshold);
            debug_printf("sys_vol:%d\n", sys_vol);
#endif
        }
        continue;

        event_polling:
        {
            hwtimer_set_trigger_time(timer, hwtimer_get_time(timer) + KEY_POLLING_INTERVAL);

#if USE_EX3D == 1
            // 检查 host app 是否通过 HID 改变了音场状态
            uint32_t host_app_sf;
            GET_SHARED_GLOBAL(host_app_sf, g_3d_on_off_t1);
            if (host_app_sf != is_3d_on) {
                is_3d_on = host_app_sf;

                // Select SF and set on/off together
                if (is_3d_on == IR_OFF) {
                    audio_ex3d_set_onoff(0);
                    current_mode_local = 0;
                    debug_printf("3d off\n");
                } else if (is_3d_on == IR_GAME) {
                    audio_ex3d_set_sf(0);
                    audio_ex3d_set_onoff(1);
                    current_mode_local = 1;
                    debug_printf("Stereo Game on (sf=0)\n");
                } else if (is_3d_on == IR_7_1_GAME) {
                    audio_ex3d_set_sf(1);
                    audio_ex3d_set_onoff(1);
                    current_mode_local = 2;
                   debug_printf("3d Game on (sf=1)\n");
                } else if (is_3d_on == IR_7_1_MOVIE) {
                    audio_ex3d_set_sf(2);
                    audio_ex3d_set_onoff(1);
                    current_mode_local = 3;
                    debug_printf("3d Movie on (sf=2)\n");
                } else {
                    audio_ex3d_set_onoff(is_3d_on != IR_OFF);
                }
                SET_SHARED_GLOBAL(g_3d_on_off_t1, is_3d_on);
            }
#endif
        }
        continue;
        }
    }
    // port_disable(p_keys);
    // port_disable(p_leds);
    // port_disable(p_leds2);
    hwtimer_free(timer);
}


chanend_t uc_dsp_to_ex3d[DSP_WORKER_COUNT];

extern int OnProcessing();
void ex3d_task(){
    channel_t c = chan_alloc();
    chanend_t c_ex3d_to_ubm = c.end_a;
    uc_ex3d_to_ubm = c.end_b;

    // set_core_high_priority_on();
     delay_milliseconds(500);
    int ret = audio_ex3d_init(NUM_USB_CHAN_OUT, NUM_USB_CHAN_OUT * FRAME_SIZE * sizeof(AUDIO_T), 48000, sizeof(AUDIO_T));
    if(ret != 0) {
        debug_printf("audio_ex3d_init() error:%d\n\r", ret);
    }

#if !UAC1_MODE
    Ex3dOffGain = -5;
    Ex3dOnGain = -5;
    Ex3dLfeGain = -5;
    // 若boot sync (0xFA) 已到达，保留flash加载的阈值；否则使用默认值-15
    if (!g_boot_lmt_threshold_loaded) {
        Ex3dLimiterThreshold = -15;
    }

    // audio_ex3d_init内部会将Ex3dExpandGain重置为库默认值(6)，这里始终用保存值恢复
    Ex3dExpandGain = g_boot_footstep_expand_gain_loaded ? g_boot_footstep_expand_gain_value : 12;
    Ex3dLimiterThreshold = g_boot_lmt_threshold_loaded ? g_boot_lmt_threshold_value : -15;

    EX3DAudio_SetOnGain(Ex3dOnGain);
    EX3DAudio_SetOffGain(Ex3dOffGain);
    // audio_ex3d_init完成后用正确的增益链重新计算并应用阈值
    {
        int32_t init_effective = Ex3dLimiterThreshold + (sys_vol + Ex3dOnGain + Ex3dExpandGain);
        if (init_effective > 0) init_effective = 0;
        if (init_effective < -35) init_effective = -35;
        EX3DAudio_SetLimiterThreshold(init_effective);
        debug_printf("ex3d_task init: lmt_threshold=%d effective=%d expand=%d\n",
                     Ex3dLimiterThreshold, init_effective, Ex3dExpandGain);
    }
    // 0xFD boot sync在init前调用了EX3DAudio_SetExpandGain，init后重新应用
    EX3DAudio_SetExpandGain(Ex3dExpandGain);

    ex3d_apply_boot_angles();
    debug_printf("===ex3d_apply_boot_angles after init\n");

    g_ex3d_init_done = 1;
    debug_printf("ex3d_task init: expand_gain=%d\n", Ex3dExpandGain);

#endif

    while (1) {
        OnProcessing(c_ex3d_to_ubm, uc_dsp_to_ex3d);
    }
}

#ifdef USE_EX3D == 1
#if defined(SPATIAL_DRAMA)
#define TD_DATA_BUFFER_ELEMENTS SPATIAL_71_DRAMA_v090h225_left_DATA_BUFFER_ELEMENTS
#elif defined(SPATIAL_GAME)
#define TD_DATA_BUFFER_ELEMENTS SPATIAL_71_GAME_v090h225_left_DATA_BUFFER_ELEMENTS
#elif defined(SPATIAL_MOVIE)
#define TD_DATA_BUFFER_ELEMENTS SPATIAL_71_MOVIE_v090h225_left_DATA_BUFFER_ELEMENTS
#elif defined(SPATIAL_MUSIC)
#define TD_DATA_BUFFER_ELEMENTS SPATIAL_71_MUSIC_v090h225_left_DATA_BUFFER_ELEMENTS
#elif defined(STEREO_2K)
#define TD_DATA_BUFFER_ELEMENTS SPATIAL_STEREO_2K_v090h090_left_DATA_BUFFER_ELEMENTS
#elif defined(STEREO_8K)
#define TD_DATA_BUFFER_ELEMENTS SPATIAL_STEREO_8K_v090h090_left_DATA_BUFFER_ELEMENTS
#elif defined(IR_SWITCHING_MODE)
#define TD_DATA_BUFFER_ELEMENTS SPATIAL_71_GAME_v090h225_left_DATA_BUFFER_ELEMENTS
#else
#error you need to define SPATIAL_DRAMA or STEREO_8K....
#endif

#if defined (IR_SWITCHING_MODE)
int dsp_worker(chanend_t c_dsp_to_ex3d,
    td_block_fir_filter_t * filters[DSP_WORKER_FILTER_PER_DATA_CH*DSP_WORKER_DATA_CH_COUNT*4]){
#else
int dsp_worker(chanend_t c_dsp_to_ex3d,
    td_block_fir_filter_t * filters[DSP_WORKER_FILTER_PER_DATA_CH*DSP_WORKER_DATA_CH_COUNT]){
#endif
    int32_t sample_buffer[DSP_WORKER_DATA_CH_COUNT][TD_DATA_BUFFER_ELEMENTS];
    td_block_fir_data_t fir_data[DSP_WORKER_DATA_CH_COUNT];
#if defined (IR_SWITCHING_MODE)
    static uint32_t ir_mode;
    GET_SHARED_GLOBAL(ir_mode, g_3d_on_off_t1);
#endif

    for(int i=0;i<DSP_WORKER_DATA_CH_COUNT;i++)
        td_block_fir_data_init(&(fir_data[i]), sample_buffer[i], TD_DATA_BUFFER_ELEMENTS);

    int32_t input[DSP_WORKER_DATA_CH_COUNT][DSP_BLOCK_LENGTH];
    int32_t output[DSP_WORKER_FILTER_PER_DATA_CH*DSP_WORKER_DATA_CH_COUNT][DSP_BLOCK_LENGTH];

    memset(sample_buffer, 0, sizeof(sample_buffer));
    while(1){
        GET_SHARED_GLOBAL(ir_mode, g_3d_on_off_t1);
        if (ir_mode) {
            //ir_mode = ir_mode & (0x03);
            if (ir_mode > 3) {
                ir_mode -= 4;
            } else {
                ir_mode -= 1;
            }
        }
        for(int ch=0;ch<DSP_WORKER_DATA_CH_COUNT;ch++){
            chan_in_buf_word(c_dsp_to_ex3d, (uint32_t *)input[ch], DSP_BLOCK_LENGTH);

            for(int d=0;d<DSP_WORKER_FILTER_PER_DATA_CH;d++){
                chan_out_buf_word(c_dsp_to_ex3d, (uint32_t *)output[DSP_WORKER_FILTER_PER_DATA_CH*ch+d], DSP_BLOCK_LENGTH);
            }
        }
        for(int ch=0;ch<DSP_WORKER_DATA_CH_COUNT;ch++){
            td_block_fir_add_data(input[ch], &(fir_data[ch]));
        }
        for(int ch=0;ch<DSP_WORKER_DATA_CH_COUNT;ch++){
            for(int d=0;d<DSP_WORKER_FILTER_PER_DATA_CH;d++){
                if(filters[(ir_mode*6) + DSP_WORKER_FILTER_PER_DATA_CH*ch+d] != 0){
                    td_block_fir_compute(output[DSP_WORKER_FILTER_PER_DATA_CH*ch+d], &(fir_data[ch]), filters[(ir_mode*6) + DSP_WORKER_FILTER_PER_DATA_CH*ch+d]);
                } else {
                    memset(output[DSP_WORKER_FILTER_PER_DATA_CH*ch+d], 0, TD_BLOCK_FIR_LENGTH*sizeof(int32_t));
                }
            }
        }
    }
}

int dsp_worker_tile(chanend_t c_dsp_to_ex3d, int worker_id){

#if defined(STEREO_2K)
    if(worker_id == 0){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_STEREO_2K_v090h090_left, 0
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 1){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                0, &td_block_fir_filter_SPATIAL_STEREO_2K_v090h090_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 2){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_STEREO_2K_v090h270_left, 0
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 3){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                0, &td_block_fir_filter_SPATIAL_STEREO_2K_v090h270_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else {
        exit(1);
    }

#elif defined(STEREO_8K)
    if(worker_id == 0){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_STEREO_8K_v090h090_left, 0
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 1){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                0, &td_block_fir_filter_SPATIAL_STEREO_8K_v090h090_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 2){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_STEREO_8K_v090h270_left, 0
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 3){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                0, &td_block_fir_filter_SPATIAL_STEREO_8K_v090h270_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else {
        exit(1);
    }


#elif defined(SPATIAL_DRAMA)
    if(worker_id == 0){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_DRAMA_v090h225_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h225_right,
                &td_block_fir_filter_SPATIAL_71_DRAMA_v090h315_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h315_right,
                &td_block_fir_filter_SPATIAL_71_DRAMA_v090h090_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h090_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 1){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_DRAMA_v090h045_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h045_right,
                &td_block_fir_filter_SPATIAL_71_DRAMA_v090h135_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h135_right,
                &td_block_fir_filter_SPATIAL_71_DRAMA_v090h270_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h270_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else {
        exit(1);
    }

#elif defined(SPATIAL_GAME)
    if(worker_id == 0){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_GAME_v090h225_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h225_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h315_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h315_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h090_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h090_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 1){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_GAME_v090h045_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h045_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h135_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h135_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h270_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h270_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else {
        exit(1);
    }

#elif defined(SPATIAL_MOVIE)
    if(worker_id == 0){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h225_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h225_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h315_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h315_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h090_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h090_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 1){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h045_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h045_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h135_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h135_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h270_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h270_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else {
        exit(1);
    }

#elif defined(SPATIAL_MUSIC)
    if(worker_id == 0){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h225_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h225_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h315_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h315_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h090_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h090_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 1){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH] =
            {
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h045_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h045_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h135_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h135_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h270_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h270_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else {
        exit(1);
    }
// IR SWITCHING MODE
#elif defined(IR_SWITCHING_MODE)
     if(worker_id == 0){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH*4] =
            {
                &td_block_fir_filter_SPATIAL_71_GAME_v090h225_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h225_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h315_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h315_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h090_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h090_right,

                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h225_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h225_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h315_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h315_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h090_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h090_right,

                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h225_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h225_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h315_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h315_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h090_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h090_right,

                0,0,//&td_block_fir_filter_SPATIAL_71_DRAMA_v090h225_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h225_right,
                0,0,//&td_block_fir_filter_SPATIAL_71_DRAMA_v090h315_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h315_right,
                0,0//&td_block_fir_filter_SPATIAL_71_DRAMA_v090h090_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h090_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else if(worker_id == 1){
        td_block_fir_filter_t * filters[DSP_WORKER_DATA_CH_COUNT*DSP_WORKER_FILTER_PER_DATA_CH*4] =
            {
                &td_block_fir_filter_SPATIAL_71_GAME_v090h045_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h045_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h135_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h135_right,
                &td_block_fir_filter_SPATIAL_71_GAME_v090h270_left, &td_block_fir_filter_SPATIAL_71_GAME_v090h270_right,

                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h045_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h045_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h135_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h135_right,
                &td_block_fir_filter_SPATIAL_71_MUSIC_v090h270_left, &td_block_fir_filter_SPATIAL_71_MUSIC_v090h270_right,

                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h045_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h045_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h135_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h135_right,
                &td_block_fir_filter_SPATIAL_71_MOVIE_v090h270_left, &td_block_fir_filter_SPATIAL_71_MOVIE_v090h270_right,

                0,0,//&td_block_fir_filter_SPATIAL_71_DRAMA_v090h045_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h045_right,
                0,0,//&td_block_fir_filter_SPATIAL_71_DRAMA_v090h135_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h135_right,
                0,0//&td_block_fir_filter_SPATIAL_71_DRAMA_v090h270_left, &td_block_fir_filter_SPATIAL_71_DRAMA_v090h270_right
            };
        dsp_worker(c_dsp_to_ex3d, filters);
    } else {
        exit(1);
    }

#endif
    return 0;
}

#endif
