Repository navigation
V1.9.13
Four faults in the LTC output and one in the Generator, all of them reported
from a single test bench and all of them fixed and then confirmed on that
same bench before release. That last part is new: the previous release went
out on simulation alone. Every change below has been run against real
hardware overnight, and the four issues behind them are closed.
The work also left the application able to explain itself: when the LTC
output corrects a frame, it now writes down what it did and why.
Upgrading. Existing configurations load unchanged and no settings were
added or removed. Five things behave differently without touching anything:
- The LTC output no longer repeats or skips frame values when the audio
buffer is longer than one timecode frame. - It keeps generating for up to a second on its own if the application
stalls, instead of repeating the last value it was given. - With MTC as the source it no longer skips a frame now and then.
- In the Generator's System Clock mode, nothing skips a frame every
forty-odd minutes any more -- on any output. - A new file,
ltc_gaps.log, appears next tosettings.json.
Fixed
LTC output: duplicate and out-of-order frame values with a large buffer (#21)
Reported by @mungewell against 1.9.12, with a PicoTimecode jamming to the
output: the jam countdown restarted over and over at 30 fps with the 4096 and
8192 buffer sizes, and succeeded with everything up to 2048.
The encoder fills the buffer ahead of time, so the frame it packs carries the
value for the moment that frame reaches the connector, not for the moment it
is packed. Its check against the timecode the rest of the application
publishes did not make the same allowance, so every frame after the first one
in a buffer looked like the output running ahead of the source: a frame was
repeated at the end of each buffer and one skipped at the start of the next.
The check now compares against the value the source will be showing when the
frame goes out. With that, a buffer of any length carries the same clean
sequence.
The threshold was never a particular buffer size, it was a buffer longer
than one frame: 2048 samples at 48 kHz are 42.7 ms against a 33.3 ms frame
at 30 fps, so 2048 was over the line before this release as well -- about
twice in twenty seconds instead of a hundred and thirty times, which is why
it went unnoticed and why it still jams. If you run 30 fps at 2048 or 25 fps
at 2048, this release fixes an occasional repeated frame you may have been
seeing for a long time.
Two smaller things were fixed with it:
- The ceiling on how far the encoder may advance a value when it starts a
frame was a fixed eight frames. An 8192-sample buffer at 30 fps with
latency compensation applied needs more than that; the ceiling now comes
from the buffer and the latency. - The sub-frame phase the application publishes to the encoder and the
timecode value it publishes alongside it are now taken from the same
place. At a playhead landing exactly on a frame boundary they could
disagree by a whole frame, and the output skipped one frame trying to
follow.
Verified by simulation across every buffer size from 32 to 8192, with and
without a latency figure from the driver, and then on hardware: @mungewell
jammed a PicoTimecode five times on each of 4096 and 8192 and once on every
smaller size, and closed the issue.
LTC output: frame numbers stalling after a display wake (#19)
Also from @mungewell, with an automated capture loop: an hour of grabs with
the screen saver disabled never failed, and waking the screen failed within a
few. The signal decoded cleanly -- the waveform was continuous, nothing was
lost -- but the frame numbers stalled for six frames and then stepped by two
until they caught up.
Waking a display can stop a timer thread on Windows for a quarter of a
second. The LTC output runs from the audio device and kept going, but the
timecode value it follows had stopped arriving, so it repeated the last value
it had and then crawled back into line one frame at a time.
The output now carries the timecode forward on its own while that value is
not arriving, for up to one second, which covers a display wake several times
over. Past a second it holds and then steps back into line when the
application recovers: a timecode that visibly stops is the right signal that
something is wrong, and better than one that keeps running over a dead
application.
This does not apply when the source itself stops -- a paused deck, the end of
a track. That is a real pause, the application says so, and the output holds
the value as it always has.
What remains in the logs after this is the machine's own stalls -- the
operating system taking the audio thread away for a quarter of a second,
which nothing here can prevent. Those are now recorded as holes, recovered
from cleanly, and named as such in the log.
LTC output from MTC: a frame number one high for ten milliseconds after every MTC sequence (#22)
An MTC timecode completes eight quarter-frames after it starts, so the value
a receiver assembles is already two frames old when it arrives, and the
decoder dates its sync point a quarter frame ahead to account for that. For
the ten milliseconds after every sequence the application was then reading
that sync point with two different rules -- one for the frame number, one
for the sub-frame phase -- and publishing a frame number one frame ahead of
where the stream actually was. The LTC output follows that number. Most of
the time its tracking absorbed the blip; occasionally it went out on the
wire as a skipped frame, with a repeated frame later to come back into line.
The frame number and the phase now come from one reading of that sync point,
with one rule. The decoder itself is unchanged, and the MTC capture replays
read as they did before.
LTC output: a correction is made only for a disagreement that lasts, and none is left half done (#22)
The output keeps its frame numbers in line with the source by comparing, at
every frame boundary, the number it is about to send with where the source
says it is. A disagreement that existed for a single engine tick -- a few
milliseconds, whatever its cause -- used to be acted on at once and cost a
frame on the wire. It now has to stand for three frame boundaries in a row
before anything is done about it. A real offset is still corrected within
about a tenth of a second; a momentary one never reaches the wire.
The same comparison had a blind spot: after a repeated frame the output could
run one frame behind the source indefinitely, because that particular offset
read as "close enough", until something else happened to push it forward --
which is what the "skipped frame a few frames later" in those captures was.
It no longer reads as close enough.
LTC output: a phase correction no longer breaks a frame
When the output has to move its frame boundary -- the source's clock
re-anchors, or the timing shifts underneath it -- it used to jump straight
there. Starting a frame part-way through is how the boundary gets aligned in
the first place, so the frame it lands on goes out incomplete and a decoder
has nothing to read until the next one arrives.
It now moves the boundary by running its bit clock slightly fast or slow
until the error is gone, instead of jumping. Every frame stays whole while it
happens. On a 12 ms correction the clock runs about 0.8 % off for under three
seconds, which is well inside what any reader follows, and the timecode
values are untouched throughout.
Two cases still jump, deliberately: after a hole in the stream, where there
is nothing left to protect, and when the timecode value itself jumps to
follow a seek, where the frame was going to be wrong anyway.
LTC output: a callback that is merely late no longer breaks the stream
When the audio interface calls late by more than about three quarters of its
period, the output used to treat that as a hole and re-align itself, which
starts a frame part-way through. But a callback that is late by less than a
whole period has not starved the interface -- there is a period of slack --
and the signal on the wire was still perfectly continuous. The re-alignment
was the only thing breaking it. At a 512-sample buffer that threshold was
8 ms, which ordinary Windows scheduling reaches on its own. Re-alignment now
needs a hole longer than a period and longer than half a frame; anything
shorter is absorbed with every frame intact. The log still records every
late callback, and now says whether it was absorbed or was a real hole.
Generator, System Clock mode: a skipped frame every forty-odd minutes on every output
In System Clock mode the Generator shows the time of day. It keeps that
smooth by running on the PC's high-resolution counter, and it used to re-align
itself with the PC's wall clock in one jump whenever the two disagreed by
50 milliseconds. Those two clocks always drift apart a little, so that jump
came round regularly -- about every 45 minutes on @mungewell's PC -- and
50 milliseconds is more than a frame at 25 fps: every output skipped a frame
each time. The LTC output's new correction log showed it as a skip at a
steady interval, which is how it was found.
The Generator now follows the wall clock continuously, a little at a time,
so the drift never builds up into a jump. Real clock changes -- the time
being set, waking from sleep, midnight -- are still followed at once. The
transport mode (play, pause, cue) was never affected.
New
The LTC output now writes down every correction it makes
ltc_gaps.log, next to settings.json, now records every frame the output
repeats, skips or snaps, and every re-alignment, with the frame it was about
to send and the position it was comparing against -- on the same timeline as
the late callbacks it already recorded. A capture that shows a wrong frame
number can now be lined up with what the application saw at that instant.
Credits
Nearly everything in this release came from one person's bench.
@mungewell (Simon Wood) reported all three faults, and then did the work
that made them findable. In order:
- The buffer-size sweep that isolated the first one -- five jams on each of
4096 and 8192, and one on every other size -- and the observation that
2048 had been over the line all along. - An LTC decoder for sigrok and PulseView, written for this and submitted
upstream, which turns a scope capture into frame numbers with sample
positions. Every capture in these threads is readable because of it. - An automated capture loop around it, thousands of five-second grabs
overnight, which is the only reason a fault occurring once in six hundred
cycles was ever characterised rather than guessed at. - The idea of putting a buffer counter into the LTC user bits, so a capture
shows which audio callback each frame was packed in. That was his request,
it shipped in the previous release, and it is what localised two of these
faults. - Reading the application's own gap log against the Windows event log, which
identified the quarter-second stalls as a scheduled system job rather than
anything in the audio path. - A GPS 1PPS reference on the bench, used to measure what the output's clock
was actually doing against true time -- the first external measurement of
the phase lock working, and the measurement that showed the interface was
innocent of the last fault. - Jitter measurements of his own hardware's MTC, including the finding that
the garbage-collection delay appears at 30 fps but not at 25.
He also put an Evertz 5300 in front of the SMPTE ST 309 date and user-bits
work from the previous release and confirmed all five modes read correctly
on real gear, which had been waiting on a reader since it was written.
Three of the four faults fixed here would have gone unnoticed without that
bench. The fourth, the one in System Clock mode, had been shipping quietly
for a long time and only surfaced because his logs were regular enough to
show a 46-minute period.