Find onset positions using spectral flux.
Detects rapid increases in spectral energy (transients/attacks).
272{
273 const size_t num_windows = (data.size() - window_size) / hop_size + 1;
274
275 if (num_windows < 2) {
276 return {};
277 }
278
279 std::vector<double> flux_values(num_windows - 1);
280
281 Eigen::VectorXd hanning_window(window_size);
282 for (uint32_t i = 0; i < window_size; ++i) {
283 hanning_window(i) = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / (window_size - 1)));
284 }
285
286 Eigen::VectorXcd prev_spectrum;
287
288 for (size_t i = 0; i < num_windows; ++i) {
289 const size_t start_idx = i * hop_size;
290 const size_t end_idx = std::min(start_idx + window_size, data.size());
291 auto window = data.subspan(start_idx, end_idx - start_idx);
292
293 Eigen::VectorXd windowed_data = Eigen::VectorXd::Zero(window_size);
294 for (size_t j = 0; j < window.size(); ++j) {
295 windowed_data(j) = window[j] * hanning_window(j);
296 }
297
298 Eigen::FFT<double> fft;
299 Eigen::VectorXcd spectrum;
300 fft.fwd(spectrum, windowed_data);
301
302 if (i > 0) {
303 double flux = 0.0;
304 for (int j = 0; j < spectrum.size(); ++j) {
305 double curr_mag = std::abs(spectrum(j));
306 double prev_mag = std::abs(prev_spectrum(j));
307 double diff = curr_mag - prev_mag;
308 if (diff > 0) {
309 flux += diff;
310 }
311 }
312 flux_values[i - 1] = flux;
313 }
314
315 prev_spectrum = spectrum;
316 }
317
318 std::vector<size_t> positions;
319
320 double max_flux = *std::max_element(flux_values.begin(), flux_values.end());
321 if (max_flux > 0.0) {
322 for (size_t i = 0; i < flux_values.size(); ++i) {
323 flux_values[i] /= max_flux;
324 }
325 }
326
327 for (size_t i = 1; i < flux_values.size() - 1; ++i) {
328 if (flux_values[i] > threshold && flux_values[i] > flux_values[i - 1] && flux_values[i] >= flux_values[i + 1]) {
329
330 size_t sample_pos = (i + 1) * hop_size;
331 positions.push_back(sample_pos);
332 }
333 }
334
335 return positions;
336}