Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Build-Depends: debhelper-compat (= 13),
plugin-hostd-dev (>= 0.1.4),
libomx-contract-dev (>= 2.10.1),
libomx-clap-lv2-dev (>= 0.8.0),
libomxdsp-dev (>= 0.18.0)
libomxdsp-dev (>= 0.21.0)
Standards-Version: 4.6.2
Homepage: https://github.com/FreeMixer/plugin-qualify
Rules-Requires-Root: no
Expand Down
18 changes: 13 additions & 5 deletions native/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ OMX_CONTRACT_VERSION := 2.10.1
# the omx-clap-core whose LV2 feature provider `make deps` builds where no omx-clap-lv2-devel is installed: the
# packages' floor
OMX_CLAP_CORE_VERSION := 0.8.0
# the omx-dsp headers that omx-clap-core's provider reads, at omx-clap-core's own floor
OMX_DSP_VERSION := 0.18.0
# the omx-dsp headers the measurers (omx_lin_to_db_d) and omx-clap-core's provider read: the packages' floor
OMX_DSP_VERSION := 0.21.0

ifeq ($(shell $(PKG_CONFIG) --exists clap && echo true),true)
CLAP_CFLAGS ?= $(shell $(PKG_CONFIG) --cflags clap)
Expand All @@ -58,6 +58,14 @@ else
OMX_CONTRACT_CFLAGS ?= -Ideps/omx-contract/include
endif

# omx-dsp's headers, the level conversions the measurers call (omx_lin_to_db_d): the installed omx-dsp-devel /
# libomxdsp-dev, else the headers `make deps` fetches
ifeq ($(shell $(PKG_CONFIG) --exists omxdsp && echo true),true)
OMX_DSP_CFLAGS ?= $(shell $(PKG_CONFIG) --cflags omxdsp)
else
OMX_DSP_CFLAGS ?= -Ideps/omx-dsp/include
endif

# the LV2 host features: <omx-clap-lv2/omx_lv2_features.h> and the static libomx-clap-lv2
ifeq ($(shell $(PKG_CONFIG) --exists omx-clap-lv2 && echo true),true)
OMX_CLAP_LV2_CFLAGS ?= $(shell $(PKG_CONFIG) --cflags omx-clap-lv2)
Expand Down Expand Up @@ -97,15 +105,15 @@ LV2_MEASURE_HDR := lv2-measure-options.h lv2-measure-pin.h lv2-measure-path.h rt

# as openmixer built it: no FMA contraction, so a figure is the same on every architecture and compiler
$(BUILD)/lv2_measure: $(LV2_MEASURE_SRC) $(LV2_MEASURE_HDR) | $(BUILD)
$(CC) $(CPPFLAGS) $(CFLAGS) -ffp-contract=off $(OMX_CONTRACT_CFLAGS) $(PLUGIN_HOSTD_CFLAGS) $(OMX_CLAP_LV2_CFLAGS) $(LILV_CFLAGS) \
$(CC) $(CPPFLAGS) $(CFLAGS) -ffp-contract=off $(OMX_CONTRACT_CFLAGS) $(OMX_DSP_CFLAGS) $(PLUGIN_HOSTD_CFLAGS) $(OMX_CLAP_LV2_CFLAGS) $(LILV_CFLAGS) \
$(LDFLAGS) -o $@ $(LV2_MEASURE_SRC) $(OMX_CLAP_LV2_LIBS) $(LILV_LIBS) -lpthread -lm -ldl

# the same, under AddressSanitizer: the leak check runs it on every way out of main
asan: $(BUILD)/asan/lv2_measure

$(BUILD)/asan/lv2_measure: $(LV2_MEASURE_SRC) $(LV2_MEASURE_HDR) | $(BUILD)
mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -fsanitize=address -fno-omit-frame-pointer -ffp-contract=off $(OMX_CONTRACT_CFLAGS) $(PLUGIN_HOSTD_CFLAGS) \
$(CC) $(CPPFLAGS) $(CFLAGS) -fsanitize=address -fno-omit-frame-pointer -ffp-contract=off $(OMX_CONTRACT_CFLAGS) $(OMX_DSP_CFLAGS) $(PLUGIN_HOSTD_CFLAGS) \
$(OMX_CLAP_LV2_CFLAGS) $(LILV_CFLAGS) $(LDFLAGS) -o $@ $(LV2_MEASURE_SRC) $(OMX_CLAP_LV2_LIBS) $(LILV_LIBS) -lpthread -lm -ldl

# the feature options' oracle, never installed
Expand All @@ -118,7 +126,7 @@ $(BUILD)/lv2_measure_path_test: lv2_measure_path.test.c lv2-measure-path.c lv2-m
$(CC) $(CPPFLAGS) $(CFLAGS) -Werror -I. $(LDFLAGS) -o $@ lv2_measure_path.test.c lv2-measure-path.c

$(BUILD)/lv2_host_probe: lv2-host-probe.c | $(BUILD)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LILV_CFLAGS) $(LDFLAGS) -o $@ $< $(LILV_LIBS) -lpthread -lm
$(CC) $(CPPFLAGS) $(CFLAGS) $(OMX_CONTRACT_CFLAGS) $(OMX_DSP_CFLAGS) $(LILV_CFLAGS) $(LDFLAGS) -o $@ $< $(LILV_LIBS) -lpthread -lm

$(BUILD)/rt_interpose.so: rt-interpose.c rt-interpose.h | $(BUILD)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -o $@ $< -ldl -lpthread
Expand Down
5 changes: 4 additions & 1 deletion native/lv2-host-probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <string.h>

#include <lilv/lilv.h>
#include <omxdsp/omx_units.h> /* omx_lin_to_db_d */
#include <lv2/core/lv2.h>
#include <lv2/worker/worker.h>

Expand Down Expand Up @@ -85,7 +86,8 @@ static double level_db_at(const float *x, uint32_t from, uint32_t n, double hz)
im += (double)x[from + i] * sin(w);
}
const double mag = 2.0 * sqrt(re * re + im * im) / (double)n;
return 20.0 * log10(mag < 1e-12 ? 1e-12 : mag);
/* a NaN magnitude stays NaN: the floor would read it as silence, and silence passes 'quieter' */
return isnan(mag) ? mag : omx_lin_to_db_d(mag, 1e-12);
}

/* ---- the host's worker ------------------------------------------------------------------
Expand Down Expand Up @@ -324,6 +326,7 @@ int main(int argc, char **argv) {
(double)from / RATE, (double)end / RATE, HOT_HARMONIC, e, b, e - b);
printf(" steady neighbour H3: %.2f dB\n", h3e - h3b);

claim(isfinite(e) && isfinite(b) && isfinite(h3e) && isfinite(h3b), "every level measured is a number (no NaN in the window)");
claim(e - b < -1.0, "the engaged plugin is measurably quieter at the resonance");
claim(fabs(h3e - h3b) < 1.5, "and leaves the steady neighbour alone");
claim(memcmp(g_in, g_bypassed, FRAMES * sizeof(float)) == 0,
Expand Down
13 changes: 7 additions & 6 deletions native/lv2-measure.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
#include "lv2-measure-path.h"
#include "lv2-measure-pin.h"
#include <omxcontract/omx_contract_limits.h>
#include <omxdsp/omx_units.h> /* omx_lin_to_db_d */
#include <lv2/atom/atom.h>

/*
Expand Down Expand Up @@ -884,7 +885,7 @@ int main(int argc, char **argv) {
if (rms < min_rms) min_rms = rms;
if (rms > max_rms) max_rms = rms;
/* Dead relative to what this plugin was doing at the start: a ratio. */
if (first_rms > 0 && rms > 0 && 20.0 * log10(rms / first_rms) < -40.0) silent_windows++;
if (first_rms > 0 && rms > 0 && omx_lin_to_db_d(rms / first_rms, 0.0) < -40.0) silent_windows++;
else if (first_rms > 0 && rms == 0.0) silent_windows++;
const long r = rss_kb();
if (r > rss_max) rss_max = r;
Expand All @@ -902,10 +903,10 @@ int main(int argc, char **argv) {
",\"firstRmsDbfs\":%.2f,\"lastRmsDbfs\":%.2f,\"minRmsDbfs\":%.2f,\"maxRmsDbfs\":%.2f"
",\"rssStartKb\":%ld,\"rssMaxKb\":%ld,\"rssGrowthKb\":%ld}",
soak_s, rate, block, windows, nonfinite_windows, silent_windows,
first_rms > 0 ? 20.0 * log10(first_rms) : -999.0,
last_rms > 0 ? 20.0 * log10(last_rms) : -999.0,
min_rms < 1e29 && min_rms > 0 ? 20.0 * log10(min_rms) : -999.0,
max_rms > 0 ? 20.0 * log10(max_rms) : -999.0,
first_rms > 0 ? omx_lin_to_db_d(first_rms, 0.0) : -999.0,
last_rms > 0 ? omx_lin_to_db_d(last_rms, 0.0) : -999.0,
min_rms < 1e29 && min_rms > 0 ? omx_lin_to_db_d(min_rms, 0.0) : -999.0,
max_rms > 0 ? omx_lin_to_db_d(max_rms, 0.0) : -999.0,
rss_start, rss_max, rss_max - rss_start);
emit_host_facts(H);
emit_feature_options();
Expand Down Expand Up @@ -1128,7 +1129,7 @@ int main(int argc, char **argv) {
"\"rms\":%.9g,\"rmsDbfs\":%.2f}",
ri ? "," : "", r, ok ? "true" : "false",
nonfinite ? "true" : "false", lat_frames, rms_out,
rms_out > 0 ? 20.0 * log10(rms_out) : -999.0);
rms_out > 0 ? omx_lin_to_db_d(rms_out, 0.0) : -999.0);
}
fprintf(jout, "}");
emit_host_facts(H);
Expand Down
2 changes: 1 addition & 1 deletion packaging/plugin-qualify.spec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ BuildRequires: pkgconfig(lv2)
BuildRequires: pkgconfig(plugin-hostd) >= 0.1.4
BuildRequires: pkgconfig(omx-contract) >= 2.10.1
BuildRequires: pkgconfig(omx-clap-lv2) >= 0.8.0
BuildRequires: pkgconfig(omxdsp) >= 0.18.0
BuildRequires: pkgconfig(omxdsp) >= 0.21.0

Requires: nodejs >= 18
Requires: python3
Expand Down
62 changes: 62 additions & 0 deletions src/native-db-copies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
/**
* The dB conversions of the native tools are omx-dsp's (`omx_lin_to_db_d` in `<omxdsp/omx_units.h>`):
* a hand-written `log10(...)` or `pow(10, ...)` in `native/*.c` is a second copy of the library's
* maths (#87). Each line left is named below with the reason it stays, and an entry whose line is
* gone fails too, so the list never outlives what it excuses.
*
* A text scan, so the negative is preceded by a positive control that proves the pattern sees a hit.
*/
import { readFileSync, readdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";

const NATIVE = join(dirname(fileURLToPath(import.meta.url)), "..", "native");

/** A call of log10/log10f, or pow/powf with base 10. */
const DB_COPY = /\blog10f?\s*\(|\bpowf?\s*\(\s*10(?:\.0*)?f?\s*,/;

/** `<file>: <the line, trimmed>` -> why it stays. */
const ALLOWED: Record<string, string> = {
"lv2-host-probe.c: const double hot = pow(10.0, HOT_EXCESS_DB / 20.0);":
"dB to linear in double: omx-dsp has omx_db_to_lin in float only, which is not bit-identical; stays until a double twin exists (#87)",
};

function hits(src: Record<string, string>): string[] {
const out: string[] = [];
for (const [file, text] of Object.entries(src))
for (const line of text.split("\n")) {
const code = line.replace(/\/\*.*?\*\/|\/\/.*$/g, "");
if (DB_COPY.test(code)) out.push(`${file}: ${line.trim()}`);
}
return out;
}

const sources = (): Record<string, string> =>
Object.fromEntries(
readdirSync(NATIVE)
.filter((n) => n.endsWith(".c"))
.map((n) => [n, readFileSync(join(NATIVE, n), "utf8")]),
);

describe("native dB conversions come from omx-dsp", () => {
it("positive control: the pattern sees a hand-written log10 and pow(10, ...), not a comment", () => {
expect(
hits({
"x.c": " return 20.0 * log10(mag);\n float g = powf(10.0f, db / 20.0f);\n /* 20 * log10(x) */\n double y = pow(2.0, 3.0);\n",
}),
).toEqual(["x.c: return 20.0 * log10(mag);", "x.c: float g = powf(10.0f, db / 20.0f);"]);
});

it("native/*.c has no log10 or pow(10, ...) but the allowlisted lines", () => {
const found = hits(sources());
expect(found.filter((h) => !(h in ALLOWED))).toEqual([]);
});

it("every allowlisted line is still there, so the list excuses nothing gone", () => {
const found = new Set(hits(sources()));
expect(Object.keys(ALLOWED).filter((k) => !found.has(k))).toEqual([]);
});
});
Loading