1 Commits

Author SHA1 Message Date
TheM14 71bec4bda3 Fix band-0 DC filter gating and improve settings UI
build / windows (push) Has been cancelled
build / release (push) Has been cancelled
2026-09-27 01:24:53 +08:00
7 changed files with 94 additions and 50 deletions
+9
View File
@@ -68,6 +68,15 @@ EJOC_API ejoc_renderer_handle EJOC_CALL ejoc_renderer_create(void);
EJOC_API void EJOC_CALL ejoc_renderer_destroy(ejoc_renderer_handle handle); EJOC_API void EJOC_CALL ejoc_renderer_destroy(ejoc_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_renderer_reset(ejoc_renderer_handle handle); EJOC_API int EJOC_CALL ejoc_renderer_reset(ejoc_renderer_handle handle);
EJOC_API int EJOC_CALL ejoc_renderer_set_threads(ejoc_renderer_handle handle, uint32_t total_threads); EJOC_API int EJOC_CALL ejoc_renderer_set_threads(ejoc_renderer_handle handle, uint32_t total_threads);
/*
Enables or disables the Ls/Rs band-0 21-tap DC compensation. The caller derives
it from the JOC downmix configuration: only configurations 3 and 4 enable the
filter. When disabled, band 0 keeps the common per-band processing (surround
delay plus -j rotation) instead of being overwritten by the FIR. The delay line
and DC history advance either way, so the flag may change between frames.
Defaults to enabled when never called.
*/
EJOC_API int EJOC_CALL ejoc_renderer_set_dc_filter(ejoc_renderer_handle handle, uint32_t enabled);
EJOC_API uint32_t EJOC_CALL ejoc_renderer_thread_count(ejoc_renderer_handle handle); EJOC_API uint32_t EJOC_CALL ejoc_renderer_thread_count(ejoc_renderer_handle handle);
EJOC_API const char* EJOC_CALL ejoc_renderer_last_error(ejoc_renderer_handle handle); EJOC_API const char* EJOC_CALL ejoc_renderer_last_error(ejoc_renderer_handle handle);
+25 -9
View File
@@ -125,6 +125,10 @@ public:
return error_[0] ? error_ : ""; return error_[0] ? error_ : "";
} }
void set_dc_filter(const bool enabled) noexcept {
dc_filter_enabled_ = enabled;
}
int process( int process(
const float* bed5, const float* bed5,
const float* lfe, const float* lfe,
@@ -305,16 +309,18 @@ private:
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
dc_buffer[20 + i] = current[i][0]; dc_buffer[20 + i] = current[i][0];
} }
for (int slot = 0; slot < 4; ++slot) { if (dc_filter_enabled_) {
Complex sum{0.0, 0.0}; for (int slot = 0; slot < 4; ++slot) {
for (int tap = 0; tap < 21; ++tap) { Complex sum{0.0, 0.0};
const Complex sample = dc_buffer[slot + tap]; for (int tap = 0; tap < 21; ++tap) {
const double cr = kDcB[tap]; const Complex sample = dc_buffer[slot + tap];
const double ci = kDcA[tap]; const double cr = kDcB[tap];
sum.re += sample.re * cr - sample.im * ci; const double ci = kDcA[tap];
sum.im += sample.re * ci + sample.im * cr; sum.re += sample.re * cr - sample.im * ci;
sum.im += sample.re * ci + sample.im * cr;
}
x_[channel][0][group + slot] = {2.0 * sum.re, 2.0 * sum.im};
} }
x_[channel][0][group + slot] = {2.0 * sum.re, 2.0 * sum.im};
} }
for (int i = 0; i < 20; ++i) { for (int i = 0; i < 20; ++i) {
surround_history_[surround][i] = dc_buffer[i + 4]; surround_history_[surround][i] = dc_buffer[i + 4];
@@ -641,6 +647,8 @@ private:
float analysis_phase_; float analysis_phase_;
alignas(64) Complex surround_delay_[2][10][64]; alignas(64) Complex surround_delay_[2][10][64];
alignas(64) Complex surround_history_[2][20]; alignas(64) Complex surround_history_[2][20];
// band-0 的 21-tap DC 补偿开关;仅 downmix 配置 3/4 由调用方置位。
bool dc_filter_enabled_ = true;
alignas(64) double lfe_delay_[kLfeDelay]; alignas(64) double lfe_delay_[kLfeDelay];
alignas(64) double matrix_previous_[15][5][64]; alignas(64) double matrix_previous_[15][5][64];
alignas(64) double synthesis_state_[15][640]; alignas(64) double synthesis_state_[15][640];
@@ -696,6 +704,14 @@ int EJOC_CALL ejoc_renderer_set_threads(ejoc_renderer_handle handle, uint32_t to
return static_cast<ejoc::Renderer*>(handle)->set_threads(total_threads); return static_cast<ejoc::Renderer*>(handle)->set_threads(total_threads);
} }
int EJOC_CALL ejoc_renderer_set_dc_filter(ejoc_renderer_handle handle, uint32_t enabled) {
if (!handle) {
return -1;
}
static_cast<ejoc::Renderer*>(handle)->set_dc_filter(enabled != 0);
return 0;
}
uint32_t EJOC_CALL ejoc_renderer_thread_count(ejoc_renderer_handle handle) { uint32_t EJOC_CALL ejoc_renderer_thread_count(ejoc_renderer_handle handle) {
if (!handle) { if (!handle) {
return 0; return 0;
+9
View File
@@ -52,6 +52,15 @@ Status rebuild_objects16(ejoc_renderer_handle handle, const joc_frame_params& pa
} }
out16->assign(static_cast<std::size_t>(JOC_OUTPUT_CHANNELS) * JOC_FRAME_SAMPLES, 0.0f); out16->assign(static_cast<std::size_t>(JOC_OUTPUT_CHANNELS) * JOC_FRAME_SAMPLES, 0.0f);
// band-0 的 21-tap DC 补偿只在 downmix 配置 3/4 下启用,其余配置 band 0
// 走与其他 band 相同的处理。
const bool dc_filter = params.dmx_config_idx == 3 || params.dmx_config_idx == 4;
if (ejoc_renderer_set_dc_filter(handle, dc_filter ? 1u : 0u) != 0) {
if (error != nullptr) {
*error = "ejoc_renderer_set_dc_filter failed";
}
return Status::fail(JOC_ERR_RENDER_FAILED, stage::kDsp, "ejoc_renderer_set_dc_filter failed");
}
const int result = ejoc_renderer_process( const int result = ejoc_renderer_process(
handle, bed5_planar, lfe, params.present_mask, n_bands, n_dpoints, slope_idx, offset_ts, handle, bed5_planar, lfe, params.present_mask, n_bands, n_dpoints, slope_idx, offset_ts,
dq.data(), params.clipgain, 0.0625f, gain, out16->data()); dq.data(), params.clipgain, 0.0625f, gain, out16->data());
+1 -1
View File
@@ -12,7 +12,7 @@
// Kept in one place: the string reported to foobar2000 and written to the log // Kept in one place: the string reported to foobar2000 and written to the log
// must not drift apart. // must not drift apart.
#define JOC_VERSION "0.2.0" #define JOC_VERSION "0.2.1"
DECLARE_COMPONENT_VERSION("JOC decoder (E-AC-3 JOC)", JOC_VERSION, DECLARE_COMPONENT_VERSION("JOC decoder (E-AC-3 JOC)", JOC_VERSION,
"Plays E-AC-3 JOC (Dolby Atmos) files: the JOC objects are " "Plays E-AC-3 JOC (Dolby Atmos) files: the JOC objects are "
+25 -19
View File
@@ -189,6 +189,7 @@ private:
} }
if (pick_file(m_hwnd, path, title, filter)) { if (pick_file(m_hwnd, path, title, filter)) {
set_text(GetDlgItem(m_hwnd, IDC_EDIT_HRTF), path); set_text(GetDlgItem(m_hwnd, IDC_EDIT_HRTF), path);
update_status();
notify(); notify();
} }
return TRUE; return TRUE;
@@ -219,6 +220,10 @@ private:
return TRUE; return TRUE;
} }
if (code == EN_CHANGE || code == CBN_SELCHANGE) { if (code == EN_CHANGE || code == CBN_SELCHANGE) {
if (id == IDC_EDIT_HRTF || id == IDC_COMBO_LAYOUT ||
id == IDC_EDIT_GAIN) {
update_status();
}
notify(); notify();
return TRUE; return TRUE;
} }
@@ -338,35 +343,36 @@ private:
void update_enabled_state() { update_status(); } void update_enabled_state() { update_status(); }
void update_status() { void update_status() {
const joc_settings::Values values = joc_settings::read(); const joc_settings::Values values = read_controls();
std::string status; std::string output_status;
if (values.output != 0) { if (values.output != 0) {
char text[96] = {}; char text[96] = {};
const unsigned channels = joc_decode::speaker_channels(values.speaker_layout); const unsigned channels = joc_decode::speaker_channels(values.speaker_layout);
std::snprintf(text, sizeof(text), "扬声器布局 %s(%u 声道)", std::snprintf(text, sizeof(text), "扬声器布局 %s(%u 声道)",
values.speaker_layout.c_str(), channels); values.speaker_layout.c_str(), channels);
status = text; output_status = text;
} else {
// Show what will actually be read, so an empty box is not a mystery.
joc_decode::Settings effective = joc_settings::current();
effective.hrtf_source =
static_cast<joc_decode::HrtfSource>(values.hrtf_source);
effective.hrtf_file = values.hrtf_file;
const std::string file = joc_decode::resolve_hrtf_file(effective);
status = std::string(joc_settings::hrtf_source_name(values.hrtf_source)) + ":" +
(file.empty() ? std::string("无法确定默认路径")
: (values.hrtf_file.empty() ? "默认 " + file : file));
} }
// Say what is actually in effect, including the parts that do not apply set_text(GetDlgItem(m_hwnd, IDC_LABEL_OUTPUT_STATUS), output_status);
// to the selected output mode, so nothing has to be greyed out.
joc_decode::Settings effective = joc_settings::current();
effective.hrtf_source =
static_cast<joc_decode::HrtfSource>(values.hrtf_source);
effective.hrtf_file = values.hrtf_file;
const std::string file = joc_decode::resolve_hrtf_file(effective);
std::string hrtf_status =
std::string(joc_settings::hrtf_source_name(values.hrtf_source)) + ":";
if (values.hrtf_file.empty()) hrtf_status += "默认";
hrtf_status += "\n" +
(file.empty() ? std::string("无法确定默认路径") : file);
set_text(GetDlgItem(m_hwnd, IDC_LABEL_STATUS), hrtf_status);
char gain[96] = {}; char gain[96] = {};
if (values.gain_enabled) { if (values.gain_enabled) {
std::snprintf(gain, sizeof(gain), "\n增益开 %.2f dB", values.gain_db); std::snprintf(gain, sizeof(gain), "增益开 %.2f dB", values.gain_db);
} else { } else {
std::snprintf(gain, sizeof(gain), "\n增益关(输出不衰减)"); std::snprintf(gain, sizeof(gain), "增益关(输出不衰减)");
} }
status += gain; set_text(GetDlgItem(m_hwnd, IDC_LABEL_GAIN_STATUS), gain);
set_text(GetDlgItem(m_hwnd, IDC_LABEL_STATUS), status);
} }
bool changed() const { bool changed() const {
+23 -21
View File
@@ -7,11 +7,11 @@
// sample page uses (SDK\foobar2000\foo_sample\foo_sample.rc). A CAPTION statement // sample page uses (SDK\foobar2000\foo_sample\foo_sample.rc). A CAPTION statement
// implies WS_CAPTION, which would draw a second frame inside the host's and shift // implies WS_CAPTION, which would draw a second frame inside the host's and shift
// every control down by the caption height -- visible but not clickable. // every control down by the caption height -- visible but not clickable.
IDD_JOC_PREFS DIALOGEX 0, 0, 330, 226 IDD_JOC_PREFS DIALOGEX 0, 0, 330, 273
STYLE DS_SETFONT | WS_CHILD STYLE DS_SETFONT | WS_CHILD
FONT 8, "Microsoft Sans Serif", 400, 0, 0x0 FONT 8, "Microsoft Sans Serif", 400, 0, 0x0
BEGIN BEGIN
GROUPBOX "输出", IDC_GROUP_OUTPUT, 7, 5, 316, 64 GROUPBOX "输出", IDC_GROUP_OUTPUT, 7, 5, 316, 61
CONTROL "双耳(HRTF)", IDC_RADIO_BINAURAL, "Button", CONTROL "双耳(HRTF)", IDC_RADIO_BINAURAL, "Button",
BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP, 16, 20, 72, 10 BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP, 16, 20, 72, 10
CONTROL "扬声器布局", IDC_RADIO_SPEAKER, "Button", CONTROL "扬声器布局", IDC_RADIO_SPEAKER, "Button",
@@ -22,31 +22,33 @@ BEGIN
LTEXT "双耳模式", IDC_LABEL_MODE, 172, 22, 42, 10 LTEXT "双耳模式", IDC_LABEL_MODE, 172, 22, 42, 10
COMBOBOX IDC_COMBO_MODE, 216, 19, 96, 90, COMBOBOX IDC_COMBO_MODE, 216, 19, 96, 90,
CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "房间尾音(秒)", IDC_LABEL_TAIL, 172, 41, 46, 10 LTEXT "房间尾音(秒)", IDC_LABEL_TAIL, 172, 41, 62, 10
EDITTEXT IDC_EDIT_TAIL, 220, 38, 40, 13, ES_AUTOHSCROLL EDITTEXT IDC_EDIT_TAIL, 238, 38, 40, 13, ES_AUTOHSCROLL
LTEXT "", IDC_LABEL_OUTPUT_STATUS, 16, 52, 300, 10
GROUPBOX "HRTF", IDC_GROUP_HRTF, 7, 74, 316, 84 GROUPBOX "HRTF", IDC_GROUP_HRTF, 7, 71, 316, 100
CONTROL "SOFA 文件", IDC_RADIO_HRTF_SOFA, "Button", CONTROL "SOFA 文件", IDC_RADIO_HRTF_SOFA, "Button",
BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP, 16, 89, 56, 10 BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP, 16, 86, 56, 10
CONTROL "Rosella 个性化模型", IDC_RADIO_HRTF_ROSELLA, "Button", CONTROL "Rosella 个性化模型", IDC_RADIO_HRTF_ROSELLA, "Button",
BS_AUTORADIOBUTTON | WS_TABSTOP, 78, 89, 78, 10 BS_AUTORADIOBUTTON | WS_TABSTOP, 78, 86, 78, 10
LTEXT "HRTF 文件(留空=默认位置)", IDC_LABEL_HRTF, 16, 106, 92, 10 LTEXT "HRTF 文件(留空=默认位置)", IDC_LABEL_HRTF, 16, 105, 106, 10
EDITTEXT IDC_EDIT_HRTF, 110, 103, 164, 13, ES_AUTOHSCROLL EDITTEXT IDC_EDIT_HRTF, 126, 102, 148, 13, ES_AUTOHSCROLL
PUSHBUTTON "浏览…", IDC_BROWSE_HRTF, 279, 103, 38, 13 PUSHBUTTON "浏览…", IDC_BROWSE_HRTF, 279, 102, 38, 13
LTEXT "留空时读组件目录下 HRTF\\binaural.sofa 或 binaural.personalized_headphone;SOFA 由渲染器内部编译,滤波组表已编入库中。", LTEXT "留空时读组件目录下 HRTF\\binaural.sofa 或 binaural.personalized_headphone;SOFA 由渲染器内部编译,滤波组表已编入库中。",
IDC_LABEL_HRTF_HINT, 16, 122, 300, 30 IDC_LABEL_HRTF_HINT, 16, 121, 300, 20
LTEXT "", IDC_LABEL_STATUS, 16, 145, 300, 20
GROUPBOX "增益", IDC_GROUP_GAIN, 7, 162, 152, 56 GROUPBOX "增益", IDC_GROUP_GAIN, 7, 176, 316, 48
CONTROL "应用增益", IDC_CHECK_GAIN, "Button", CONTROL "应用增益", IDC_CHECK_GAIN, "Button",
BS_AUTOCHECKBOX | WS_TABSTOP, 16, 178, 58, 10 BS_AUTOCHECKBOX | WS_TABSTOP, 16, 192, 58, 10
LTEXT "dB", IDC_LABEL_GAIN, 80, 178, 12, 10 LTEXT "dB", IDC_LABEL_GAIN, 80, 192, 12, 10
EDITTEXT IDC_EDIT_GAIN, 96, 175, 40, 13, ES_AUTOHSCROLL EDITTEXT IDC_EDIT_GAIN, 96, 189, 40, 13, ES_AUTOHSCROLL
LTEXT "", IDC_LABEL_GAIN_STATUS, 145, 192, 171, 10
LTEXT "双耳渲染后可能超过 0 dBFS,可用负值衰减。", LTEXT "双耳渲染后可能超过 0 dBFS,可用负值衰减。",
IDC_LABEL_GAIN_HINT, 16, 194, 134, 20 IDC_LABEL_GAIN_HINT, 16, 208, 300, 10
GROUPBOX "其它", IDC_GROUP_MISC, 165, 162, 158, 56 GROUPBOX "其它", IDC_GROUP_MISC, 7, 229, 316, 38
LTEXT "ffmpeg.exe", IDC_LABEL_FFMPEG, 173, 178, 44, 10 LTEXT "ffmpeg.exe", IDC_LABEL_FFMPEG, 16, 245, 44, 10
EDITTEXT IDC_EDIT_FFMPEG, 216, 175, 62, 13, ES_AUTOHSCROLL EDITTEXT IDC_EDIT_FFMPEG, 64, 242, 210, 13, ES_AUTOHSCROLL
PUSHBUTTON "浏览…", IDC_BROWSE_FFMPEG, 282, 175, 35, 13 PUSHBUTTON "浏览…", IDC_BROWSE_FFMPEG, 279, 242, 38, 13
LTEXT "", IDC_LABEL_STATUS, 173, 194, 144, 20
END END
+2
View File
@@ -12,6 +12,7 @@
#define IDC_COMBO_MODE 2016 #define IDC_COMBO_MODE 2016
#define IDC_LABEL_TAIL 2017 #define IDC_LABEL_TAIL 2017
#define IDC_EDIT_TAIL 2018 #define IDC_EDIT_TAIL 2018
#define IDC_LABEL_OUTPUT_STATUS 2019
#define IDC_GROUP_HRTF 2020 #define IDC_GROUP_HRTF 2020
#define IDC_RADIO_HRTF_SOFA 2021 #define IDC_RADIO_HRTF_SOFA 2021
@@ -26,6 +27,7 @@
#define IDC_LABEL_GAIN 2032 #define IDC_LABEL_GAIN 2032
#define IDC_EDIT_GAIN 2033 #define IDC_EDIT_GAIN 2033
#define IDC_LABEL_GAIN_HINT 2034 #define IDC_LABEL_GAIN_HINT 2034
#define IDC_LABEL_GAIN_STATUS 2035
#define IDC_GROUP_MISC 2040 #define IDC_GROUP_MISC 2040
#define IDC_LABEL_FFMPEG 2041 #define IDC_LABEL_FFMPEG 2041