Open eatnumber1 opened 2 years ago
I've never had good results with the loop detector, and I've never really figured out quite how it works. I think in general this is better addressed with NSF2/NSFe metadata giving track lengths, but my long term plan was to leave this as-is, and write a new loop detector for NSFPlay 3 eventually.
I've been working on deciphering how nsfplay's loop detection works. It's not documented, and until I started poking around I'd never seen it work.
This is mostly some disconnected notes from my code diving / debugging sessions with it, but I'm dropping it here because as far as I can tell this is the closest to documentation that's available.
To enable detection, set
AUTO_DETECT=1
in in_yansf.ini. "Enable playtime detection" in the settings does this.There's two detectors: BasicDetector (default) and NESDetectorEX (
DETECT_ALT=1
, or uncheck "Use the default loop detection algorithm" in the settings).I didn't dive too deeply into NESDetectorEX, but it seems to run BasicDetector over every channel separately (e.g. DPCM, SQR0, SQR1, etc.), then joins the results. Everything below is about BasicDetector.
It uses a sliding window as configured by
DETECT_TIME
(default 30s). Units are milliseconds. The maximum window is 16 KiB in size, not configurable. How much time this is depends on the playback speed, but there's a fixed maximumDETECT_TIME
due to this.Every
DETECT_INT
milliseconds (no setting in the UI for this, default 5s), the detector matches against the lastDETECT_TIME
milliseconds of audio. If it is identical, it is considered a loop.Detection doesn't even start until a
DETECT_TIME
period has passed. After oneDETECT_TIME
has passed, on the next detection call (as controlled byDETECT_INT
), a loop is attempted to be detected. I think there's a bug however, as it seems that the immediate next detection call is skipped (so you've actually got to wait twoDETECT_INT
periods after the buffer is full).If a loop is detected however, the time that the loop occurred is not returned. Instead, the loop end time seems to be shifted forward to the time the loop detector that detected the loop ran.
Here's an example:
Config:
Song is mod_depth_mod.zip, it has a loop at about T+7s.
Behavior: At T+21s a loop detected to occur at T+12s. The real loop occurred at T+7s, but when the detector ran the first time at T+7s, a
DETECT_TIME
period hadn't passed, and when it ran again at T+14s, I think it's a bug. When it ran a third time at T+21s it only looked back as far as T+1s (I suspect an off-by-one error between the T+12s detection time and the T+1s lookback max). It then saw that T+2s to T+12s was a loop, so it set the loop end time to T+12s.The result of this behavior is the following limitations:
DETECT_TIME
.DETECT_TIME
is shorter than the song's actual loop time, no loop will be foundDETECT_TIME
is longer than the song's actual loop time, more than two loops of the real song will need to be played for detection to occur.DETECT_INT
andDETECT_WINDOW
setting than they are actually the first time the song looped.Based on all this, I can't really come up with a way to configure the loop detector such that it will find the earliest possible time a song loops, even for songs I know have a loop within a fixed time bound. The only reasonable use I can come up with for the loop detector as-is is as a heuristic way to stop a relatively short looping song from looping so many times it gets annoying (which, not-so-coincidentally, also seems to be the intended use of it).