feat: add independent full-band activity workflow

This commit is contained in:
2026-08-29 13:56:09 +09:00
parent 3ebc0f84e0
commit 51e346a477
8 changed files with 2176 additions and 50 deletions
+103 -45
View File
@@ -4,7 +4,8 @@ const DIXON_2006_URL =
"https://www.dafx.de/paper-archive/2006/papers/p_133.pdf";
const BOECK_KREBS_SCHEDL_2012_URL =
"https://www.cp.jku.at/research/papers/Boeck_etal_ISMIR_2012.pdf";
const DEFAULT_MAXIMUM_OUTPUT_BYTES = 64 * 1024 * 1024;
export const DEFAULT_MAXIMUM_SPECTRAL_FLUX_OUTPUT_BYTES =
64 * 1024 * 1024;
function requireConfigurationObject(value, label) {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -92,6 +93,90 @@ function checkedByteCount(elementCount, bytesPerElement, label) {
return byteCount;
}
export function estimateDixonSpectralFluxWorkload(options) {
const configuration = requireConfigurationObject(options, "options");
const sampleCount = requireNonNegativeSafeInteger(
requireOwn(configuration, "sampleCount"),
"sampleCount",
);
const fftSize = requirePowerOfTwo(
requireOwn(configuration, "fftSize"),
"fftSize",
);
const hopSize = requireNonNegativeSafeInteger(
requireOwn(configuration, "hopSize"),
"hopSize",
);
if (hopSize < 1 || hopSize > fftSize) {
throw new RangeError("hopSize must be between 1 and fftSize, inclusive.");
}
const maximumOutputBytes =
configuration.maximumOutputBytes ??
DEFAULT_MAXIMUM_SPECTRAL_FLUX_OUTPUT_BYTES;
if (!Number.isSafeInteger(maximumOutputBytes) || maximumOutputBytes <= 0) {
throw new RangeError("maximumOutputBytes must be a positive safe integer.");
}
const frameCount = sampleCount < fftSize
? 0
: 1 + Math.floor((sampleCount - fftSize) / hopSize);
const binCount = fftSize;
const positiveDifferenceElements =
checkedElementCount(frameCount, binCount);
const positiveDifferenceBytes = checkedByteCount(
positiveDifferenceElements,
Float64Array.BYTES_PER_ELEMENT,
"positiveDifferences",
);
const rawOdfBytes = checkedByteCount(
frameCount,
Float64Array.BYTES_PER_ELEMENT,
"rawOdf",
);
const frameTimingBytes = checkedByteCount(
frameCount * 3,
Float64Array.BYTES_PER_ELEMENT,
"frame timing",
);
const outputBytes =
positiveDifferenceBytes + rawOdfBytes + frameTimingBytes;
if (!Number.isSafeInteger(outputBytes)) {
throw new RangeError(
"spectral-flux output byte count exceeds safe integer range.",
);
}
if (outputBytes > maximumOutputBytes) {
throw new RangeError(
`spectral flux requires ${outputBytes.toLocaleString()} output bytes; ` +
`maximumOutputBytes is ${maximumOutputBytes.toLocaleString()}.`,
);
}
const workspaceElements = frameCount === 0 ? 0 : fftSize * 4;
const workspaceBytes = checkedByteCount(
workspaceElements,
Float64Array.BYTES_PER_ELEMENT,
"spectral-flux workspace",
);
const estimatedPeakTypedArrayBytes = outputBytes + workspaceBytes;
if (!Number.isSafeInteger(estimatedPeakTypedArrayBytes)) {
throw new RangeError(
"spectral-flux peak typed-array byte count exceeds safe integer range.",
);
}
return Object.freeze({
sampleCount,
frameCount,
binCount,
positiveDifferenceElements,
positiveDifferenceBytes,
rawOdfBytes,
frameTimingBytes,
outputBytes,
maximumOutputBytes,
workspaceBytes,
estimatedPeakTypedArrayBytes,
});
}
/**
* Compute Simon Dixon's 2006 magnitude spectral-flux onset detection function.
*
@@ -123,51 +208,24 @@ export function computeDixonSpectralFlux(options) {
if (typeof channelId !== "string" || channelId.length === 0) {
throw new TypeError("channelId must be a non-empty string.");
}
const maximumOutputBytes = configuration.maximumOutputBytes ?? DEFAULT_MAXIMUM_OUTPUT_BYTES;
if (!Number.isSafeInteger(maximumOutputBytes) || maximumOutputBytes <= 0) {
throw new RangeError("maximumOutputBytes must be a positive safe integer.");
}
const frameCount = samples.length < fftSize
? 0
: 1 + Math.floor((samples.length - fftSize) / hopSize);
const binCount = fftSize;
const positiveDifferenceElements = checkedElementCount(frameCount, binCount);
const positiveDifferenceBytes = checkedByteCount(
positiveDifferenceElements,
Float64Array.BYTES_PER_ELEMENT,
"positiveDifferences",
);
const rawOdfBytes = checkedByteCount(
const workload = estimateDixonSpectralFluxWorkload({
sampleCount: samples.length,
fftSize,
hopSize,
maximumOutputBytes: configuration.maximumOutputBytes,
});
const {
frameCount,
Float64Array.BYTES_PER_ELEMENT,
"rawOdf",
);
const frameTimingBytes = checkedByteCount(
frameCount * 3,
Float64Array.BYTES_PER_ELEMENT,
"frame timing",
);
const outputBytes = positiveDifferenceBytes + rawOdfBytes + frameTimingBytes;
if (!Number.isSafeInteger(outputBytes)) {
throw new RangeError("spectral-flux output byte count exceeds safe integer range.");
}
if (outputBytes > maximumOutputBytes) {
throw new RangeError(
`spectral flux requires ${outputBytes.toLocaleString()} output bytes; ` +
`maximumOutputBytes is ${maximumOutputBytes.toLocaleString()}.`,
);
}
const workspaceElements = frameCount === 0 ? 0 : fftSize * 4;
const workspaceBytes = checkedByteCount(
workspaceElements,
Float64Array.BYTES_PER_ELEMENT,
"spectral-flux workspace",
);
const estimatedPeakTypedArrayBytes = outputBytes + workspaceBytes;
if (!Number.isSafeInteger(estimatedPeakTypedArrayBytes)) {
throw new RangeError("spectral-flux peak typed-array byte count exceeds safe integer range.");
}
binCount,
positiveDifferenceElements,
positiveDifferenceBytes,
rawOdfBytes,
frameTimingBytes,
outputBytes,
maximumOutputBytes,
workspaceBytes,
estimatedPeakTypedArrayBytes,
} = workload;
const positiveDifferences = new Float64Array(positiveDifferenceElements);
const rawOdf = new Float64Array(frameCount);