// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.
package net.avadeaux.klipspringer.split;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import net.avadeaux.klipspringer.codec.*;
public class Detector implements PcmWriter {
public class Track {
public final long startFrame;
public final long endFrame;
public Track(long startFrame, long endFrame) {
this.startFrame = startFrame;
this.endFrame = endFrame;
}
}
private final PcmFormat format;
private final int atomFrames;
private final long[] sweep; // accumulated atom sums, length is power of 2
private final long silence; // max sum of silent atom
private final int minTrackAtoms;
private final int minGapAtoms;
private final ArrayList<Track> tracks = new ArrayList<Track>();
private final long startFrame, endFrame;
private long frame; // index of current frame
private long trackStart; // -1 when scanning gap
private long atomSum = 0;
private long sweepSum = 0;
private int xatoms = 0; // number of silent in track, or non-silent in gap
public Detector(PcmFormat format,
int atomFrames,
double silenceLevel,
double minTrackTime,
double minGapTime,
long startFrame,
long endFrame)
{
this.format = format;
this.atomFrames = atomFrames;
sweep = new long[(int) Math.ceil(format.rate()/atomFrames*0.1)];
silence = sweep.length * (long) (atomFrames*format.channels()*(((1 << format.bips()-1)-1)*silenceLevel));
minTrackAtoms = (int) (minTrackTime*format.rate()/atomFrames);
minGapAtoms = (int) (minGapTime*format.rate()/atomFrames);
this.startFrame = frame = startFrame;
this.endFrame = endFrame;
trackStart = frame == 0
? -1 // start in silence if scanning from start of input
: frame; // otherwise not
}
public boolean write(ByteBuffer data) {
PcmBuffer pb = PcmBuffer.of(data, format, Device.BitLayout.LSB);
while (true) { // loop over atoms
long endAtom = frame - frame%atomFrames + atomFrames;
while (frame < endAtom) {
if (frame == endFrame) { return false; }
if (!data.hasRemaining()) { return true; }
for (int i = 0; i < format.channels(); i++) {
atomSum += Math.abs((long) pb.getSample());
}
frame++;
}
int i = ((int) frame/atomFrames) % sweep.length;
sweepSum -= sweep[i];
sweepSum += sweep[i] = atomSum;
if (sweepSum > silence) { // sweep is non-silent
if (trackStart < 0) { // not in track, maybe start one
if (++xatoms == minTrackAtoms) {
trackStart = Math.max(startFrame, frame - xatoms*atomFrames);
xatoms = 0;
}
} else { // continue track
xatoms = 0;
}
} else { // atom is silent
if (trackStart < 0) { // not in track
xatoms = 0;
} else { // maybe end track
if (++xatoms == minGapAtoms) {
tracks.add(new Track(trackStart, frame - xatoms*atomFrames));
trackStart = -1;
xatoms = 0;
}
}
}
atomSum = 0;
}
}
public void close() {
if (trackStart >= 0) {
tracks.add(new Track(trackStart, frame));
trackStart = -1;
}
}
public Track[] result() {
close();
return tracks.toArray(new Track[tracks.size()]);
}
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home