<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>datum blog</title><description>Canonical answers and technical essays from the datum ecosystem.</description><link>https://datum.film/</link><language>en-au</language><item><title>What actually happens when you press &apos;copy&apos;</title><link>https://datum.film/blog/what-happens-press-copy/</link><guid isPermaLink="true">https://datum.film/blog/what-happens-press-copy/</guid><description>Between the first byte read and the final checksum, five distinct phases run. Skipping any of them is how data gets lost.</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You drag a folder onto a drive. A progress bar fills up. The OS tells you the copy is complete. You eject, label, and ship.&lt;/p&gt;
&lt;p&gt;That workflow is fine for moving vacation photos. It is not fine for moving camera originals that cannot be recreated. The difference is not about the files themselves. It is about what &quot;complete&quot; means.&lt;/p&gt;
&lt;p&gt;A robust copy pipeline has five phases. Each one exists because a specific, documented failure mode demanded it. When you understand what each phase does and what breaks without it, you stop trusting progress bars and start trusting evidence.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Five-phase copy pipeline: enumerate, hash, write, verify, and manifest.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Phase 1: Enumerate&lt;/h2&gt;
&lt;p&gt;Before a single byte moves, the pipeline walks the source and builds a manifest of every file and directory it intends to copy. File paths, sizes, timestamps, permissions. The full tree, recorded before anything changes.&lt;/p&gt;
&lt;p&gt;This sounds trivial. It is not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The failure it prevents: partial copies that look complete.&lt;/strong&gt; If you start copying files as you discover them, you have no way to know when you are done. A camera card with 400 clips and a nested folder structure can be partially traversed if the card is pulled early, if a directory is locked by another process, or if a filesystem error makes a subdirectory invisible. Without a complete enumeration up front, you cannot compare what you copied against what existed. You land with 398 files, the copy tool reports success, and two clips are silently missing.&lt;/p&gt;
&lt;p&gt;Enumeration also catches problems before the slow part starts. A destination with insufficient free space, a source with unreadable directories, filename conflicts on the target volume. All of these are cheaper to discover before you start moving terabytes.&lt;/p&gt;
&lt;p&gt;The output of this phase is a source manifest: a list of everything that should exist on the other side when the job is done.&lt;/p&gt;
&lt;h2&gt;Phase 2: Hash&lt;/h2&gt;
&lt;p&gt;With the source manifest in hand, the pipeline reads every source file and computes a cryptographic hash. xxHash, SHA-256, MD5, or all three depending on the requirements. The hash is a fingerprint of the file&apos;s exact contents. Change one bit and the hash changes completely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The failure it prevents: copying corruption faithfully.&lt;/strong&gt; If you only hash after copying, you can prove the destination matches what you sent. But you cannot prove the source was healthy when you read it. Camera firmware bugs, failing flash controllers, and degrading media all produce files that are internally corrupt but still readable. A source hash taken before the copy gives you a reference point. If the source file is already damaged, you have evidence of when the damage existed. Without it, you discover the corruption downstream and have no way to determine whether it happened during the copy or was present on the card.&lt;/p&gt;
&lt;p&gt;Source hashing is also the foundation of deduplication. On multi-day shoots, the same LUT files, sidecar XMLs, and camera metadata appear on every card. Hashing the source lets the pipeline recognise duplicates without byte-comparing files across volumes.&lt;/p&gt;
&lt;p&gt;This phase is the slowest part of the pipeline for large jobs because it requires a full sequential read of every source byte. It is also the most valuable. The source hash is the single most important piece of evidence in the entire chain.&lt;/p&gt;
&lt;h2&gt;Phase 3: Write&lt;/h2&gt;
&lt;p&gt;Now the bytes move. The pipeline reads each source file and writes it to the destination. This is the phase that your operating system&apos;s copy dialog shows you. It is also the phase that most people confuse with the entire process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The failure it prevents: nothing, on its own.&lt;/strong&gt; The write phase by itself only guarantees that your software asked the operating system to place bytes on the destination. It does not guarantee those bytes arrived correctly. It does not guarantee every file was included. It does not guarantee the destination is readable.&lt;/p&gt;
&lt;p&gt;This is not a criticism. Moving data is necessary. But the write phase is just transportation. Treating it as the whole pipeline is like treating the flight as the whole logistics chain: you still need to verify the cargo manifest at the other end.&lt;/p&gt;
&lt;p&gt;Modern pipelines optimise the write phase aggressively. Parallel writes to multiple destinations from a single source read. Adaptive buffer sizing based on drive throughput. Write ordering that keeps the source drive&apos;s read head sequential even when multiple destinations have different performance characteristics. All of this matters for speed. None of it matters for correctness without the phases on either side.&lt;/p&gt;
&lt;p&gt;One detail that trips people up: operating systems use write caches. When your OS reports that a file write is complete, it often means the bytes have been accepted into a memory buffer, not that they have been committed to the physical media. Ejecting a drive immediately after a copy finishes can interrupt the cache flush. The OS said &quot;done&quot; while bytes were still in flight. This is not a theoretical risk. It is one of the most common causes of data loss on set.&lt;/p&gt;
&lt;h2&gt;Phase 4: Verify&lt;/h2&gt;
&lt;p&gt;The pipeline reads every file back from the destination and hashes it. Then it compares the destination hash against the source hash from Phase 2. A match means the bytes on the destination are identical to the bytes that were on the source at the time of hashing. A mismatch means something changed during transit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The failure it prevents: silent write errors.&lt;/strong&gt; Hard drives, SSDs, USB controllers, Thunderbolt cables, bus adapters, and RAID controllers can all introduce errors during writes. These errors are rare per-byte, but when you are writing hundreds of gigabytes, &quot;rare per-byte&quot; becomes &quot;routine per-job.&quot; A single bit flip in a compressed video frame can destroy the frame or cause a decoder crash. A single bit flip in a metadata header can make an entire file unreadable.&lt;/p&gt;
&lt;p&gt;Verification catches all of these. It is the only phase that proves the destination is healthy. Not &quot;probably fine,&quot; not &quot;no errors were reported.&quot; Actually, independently verified.&lt;/p&gt;
&lt;p&gt;There is a subtle distinction here that matters. Some tools hash during the write: they compute a checksum from the bytes as they flow through memory on their way to the destination. This is better than nothing. But it proves what your software sent, not what the drive received. A true verification requires an independent read-back from the physical media. The drive has to seek back to the beginning, read the file again, and produce a hash that matches. That second read is the evidence.&lt;/p&gt;
&lt;p&gt;The cost is time. Verification roughly doubles the total duration of a copy job because it requires reading the entire dataset from the destination. On a tight schedule, this is the phase people are tempted to skip. It is also the phase whose absence causes the most damage.&lt;/p&gt;
&lt;h2&gt;Phase 5: Manifest&lt;/h2&gt;
&lt;p&gt;With source hashes, destination hashes, and verification results in hand, the pipeline writes a manifest. This is a structured record of what was copied, when, from where, to where, and whether each file passed verification. Formats vary. ASC MHL is the film industry standard. Some pipelines write CSV logs, XML reports, or database entries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The failure it prevents: unverifiable claims.&lt;/strong&gt; Without a manifest, you are asking people to trust your word that the copy was verified. Three months from now, when a file is missing or a frame is corrupt, nobody will remember whether this particular drive was verified at copy time. The manifest is the receipt. It is timestamped, it contains the hashes, and it can be independently checked against the files on the drive.&lt;/p&gt;
&lt;p&gt;Manifests also enable the concept of a chain of custody. When a drive ships from set to a post facility, the facility can verify the manifest against the files they received. If everything matches, they know the drive was not altered in transit. If something mismatches, they know exactly which files are suspect. Without a manifest, the only option is to call the DIT and ask &quot;did you check everything?&quot; That is not a workflow. That is hoping.&lt;/p&gt;
&lt;p&gt;A good manifest also records what was not copied. Files that were skipped, directories that failed enumeration, and hashes that did not match on the first attempt. This negative evidence is just as important as the positive. Knowing that a job completed with zero errors is different from not knowing whether there were errors.&lt;/p&gt;
&lt;h2&gt;Why your OS skips three of these&lt;/h2&gt;
&lt;p&gt;When you drag a folder in Finder or Explorer, here is what actually runs: enumerate (partially, as it discovers files), write. That is it. No source hash. No read-back verification. No manifest.&lt;/p&gt;
&lt;p&gt;The OS copies files correctly almost all of the time. For documents, downloads, and application data, that is fine. The failure rate is low enough that it does not justify the time cost of hashing and verifying every file.&lt;/p&gt;
&lt;p&gt;Camera originals are different. They cannot be re-created. They represent days of work by dozens of people. And they are large enough that &quot;rare per-byte&quot; errors become statistically likely over the course of a job. The risk profile is completely different, and it demands a process that matches.&lt;/p&gt;
&lt;h2&gt;The cost of skipping&lt;/h2&gt;
&lt;p&gt;Each phase has a time cost. Enumeration is fast, usually seconds. Source hashing is slow, proportional to the data volume. Writing is slow, limited by the slowest drive in the chain. Verification is slow, another full read pass. Manifest generation is fast, usually seconds.&lt;/p&gt;
&lt;p&gt;A full five-phase pipeline takes roughly twice as long as a simple copy. On a 2TB card, that might mean 40 minutes instead of 20. That feels expensive in the moment, sitting in a truck at midnight waiting for a progress bar.&lt;/p&gt;
&lt;p&gt;But the alternative is not &quot;20 minutes and it works.&quot; The alternative is &quot;20 minutes and you hope it works.&quot; The next time someone discovers it did not work, the cost is a reshoot, a missed delivery, or footage that simply no longer exists.&lt;/p&gt;
&lt;p&gt;The five phases exist because people learned this the hard way. Every phase was added in response to a real loss. Enumeration was added after partial copies were shipped as complete. Source hashing was added after corrupt originals were faithfully duplicated to three drives. DITs writing bash scripts at 3am discovered every one of these failure modes the hard way. Verification was added after silent write errors destroyed footage that tested fine at the source. Manifests were added after facilities received drives with no evidence and no recourse.&lt;/p&gt;
&lt;p&gt;&quot;Copy complete&quot; is not a statement about your data. It is a statement about one phase of a five-phase process. The question is not whether the copy finished. The question is whether you have evidence that it worked.&lt;/p&gt;
</content:encoded></item><item><title>What DIT bash scripts got right (and where they broke)</title><link>https://datum.film/blog/dit-bash-scripts/</link><guid isPermaLink="true">https://datum.film/blog/dit-bash-scripts/</guid><description>Before dedicated copy tools existed, DITs built their own ingest pipelines in bash. The scripts defined the spec. They also defined the failure modes.</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In 2006, the RED ONE started shipping with CF card media. No ingest software existed for it. The camera assistant handed the card to someone with a MacBook Pro and no job title, and the footage needed to get onto a drive, then a second drive, with proof that all copies were identical. If any copy was wrong, the day&apos;s shoot was unrecoverable.&lt;/p&gt;
&lt;p&gt;The solution was Terminal.&lt;/p&gt;
&lt;h2&gt;rsync and a prayer&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rsync&lt;/code&gt; was the default, not because it was the best option, but because it shipped with macOS and handled recursive copies with checksums. The command was simple enough to memorise:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -avP --checksum /Volumes/RED_CARD/ /Volumes/RAID_A/DAY_01/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was the entire ingest pipeline. One line.&lt;/p&gt;
&lt;p&gt;Verification was manual. Run &lt;code&gt;md5sum&lt;/code&gt; or &lt;code&gt;shasum&lt;/code&gt; against both source and destination, diff the output in a second Terminal window. If the hashes matched, label the drive, case it, move to the next card.&lt;/p&gt;
&lt;p&gt;This was the complete data management workflow on a significant number of productions through the late 2000s. It was slow and manual, but it was correct.&lt;/p&gt;
&lt;h2&gt;Then the scripts got longer&lt;/h2&gt;
&lt;p&gt;Scripts grew incrementally. The same five commands ran for every card, so they went into a &lt;code&gt;.sh&lt;/code&gt; file. Then a timestamp in the log. Then a free space check before copy. Then audio notifications on completion and failure.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# offload.sh — v11 (don&apos;t ask about v4 through v8)

SRC=&quot;$1&quot;
DST=&quot;$2&quot;
LOG=&quot;$DST/copy_log_$(date +%Y%m%d_%H%M%S).txt&quot;

echo &quot;=== OFFLOAD START ===&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;
echo &quot;Source: $SRC&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;
echo &quot;Destination: $DST&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;
echo &quot;Started: $(date)&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;

df -h &quot;$DST&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;

rsync -avP --checksum &quot;$SRC/&quot; &quot;$DST/&quot; 2&amp;gt;&amp;amp;1 | tee -a &quot;$LOG&quot;

echo &quot;=== VERIFICATION ===&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;
cd &quot;$SRC&quot; &amp;amp;&amp;amp; find . -type f -exec md5 {} \; | sort &amp;gt; /tmp/src_hashes.txt
cd &quot;$DST&quot; &amp;amp;&amp;amp; find . -type f -exec md5 {} \; | sort &amp;gt; /tmp/dst_hashes.txt

diff /tmp/src_hashes.txt /tmp/dst_hashes.txt &amp;gt;&amp;gt; &quot;$LOG&quot; 2&amp;gt;&amp;amp;1

if [ $? -eq 0 ]; then
    echo &quot;VERIFIED — all checksums match&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;
    afplay /System/Library/Sounds/Glass.aiff
else
    echo &quot;*** MISMATCH DETECTED ***&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;
    afplay /System/Library/Sounds/Sosumi.aiff
    afplay /System/Library/Sounds/Sosumi.aiff
    afplay /System/Library/Sounds/Sosumi.aiff
fi

echo &quot;Finished: $(date)&quot; &amp;gt;&amp;gt; &quot;$LOG&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The triple error sound is a real pattern from production scripts. It exists because a single &lt;code&gt;Glass.aiff&lt;/code&gt; is easy to sleep through, and a silent copy failure means lost time. Repeated alerts solved a real failure mode: the operator not noticing.&lt;/p&gt;
&lt;h2&gt;What the scripts knew&lt;/h2&gt;
&lt;p&gt;Collected together, DIT bash scripts from 2006 to 2012 form an accidental specification for on-set data management.&lt;/p&gt;
&lt;p&gt;Every script had the same structure. Copy the files. Hash the copies. Compare the hashes. Log everything. Report the result. Independent practitioners converged on the five-phase copy pipeline without coordinating. Some scripts added RAID health checks, free space calculations, automatic folder structures keyed to camera roll numbers, or email alerts over hotel WiFi. The core was always the same five operations.&lt;/p&gt;
&lt;p&gt;The convergence is the interesting part. No spec existed. The requirements emerged from the work: take data off a temporary medium, put it somewhere safer, prove the copy is correct.&lt;/p&gt;
&lt;h2&gt;Why some DITs never stopped&lt;/h2&gt;
&lt;p&gt;Some experienced DITs still use scripts despite knowing every graphical tool on the market. They have evaluated them, tested them, recommended them to others. Then they go back to their own scripts.&lt;/p&gt;
&lt;p&gt;The reason is auditability.&lt;/p&gt;
&lt;p&gt;A self-written script has no ambiguity about what it does. It runs &lt;code&gt;rsync&lt;/code&gt; with &lt;code&gt;--checksum&lt;/code&gt;, not a faster mode that skips verification. It hashes source and destination independently rather than trusting a copy tool&apos;s exit code. The log format is explicit because the operator chose every field. The failure modes are known because the operator wrote the error handling (or didn&apos;t, and learned from the result).&lt;/p&gt;
&lt;p&gt;A graphical tool is a black box. When a producer asks &quot;are you sure that copy is good?&quot; the answer &quot;the app showed a green checkmark&quot; means trusting someone else&apos;s definition of &quot;good.&quot; &quot;Checksum verified&quot; is not a yes-or-no question. It matters how the hash was computed and when. &quot;I hashed source and destination independently with SHA-256 and diffed the output, here&apos;s the log&quot; is a different class of answer.&lt;/p&gt;
&lt;p&gt;This is not about preference. It is about the difference between observable and opaque verification.&lt;/p&gt;
&lt;h2&gt;What the scripts couldn&apos;t do&lt;/h2&gt;
&lt;p&gt;The scripts had hard limits, and those limits tracked production scale.&lt;/p&gt;
&lt;p&gt;A single bash script handles four cards from one camera. It does not handle eight cameras shooting to two card slots each, dual-system audio on a Sound Devices recorder, and six more cards arriving from second unit. That scenario requires parallel copies with backpressure, queue management, priority ordering, progress tracking across multiple destinations, and handoff of verified media to editorial before the last card finishes copying.&lt;/p&gt;
&lt;p&gt;All of that can be built in bash. People have. The result is a few thousand lines of shell that only the author understands, that breaks when a macOS update moves the path to &lt;code&gt;md5&lt;/code&gt;, and that has no error handling for failure modes that haven&apos;t occurred yet.&lt;/p&gt;
&lt;p&gt;The specific failure modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No concurrency model.&lt;/strong&gt; Bash scripts run sequentially. Parallelising with background jobs and &lt;code&gt;wait&lt;/code&gt; creates race conditions in log output and error handling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No state persistence.&lt;/strong&gt; If the script crashes mid-copy, there is no record of which files completed. The entire offload restarts from scratch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform fragility.&lt;/strong&gt; &lt;code&gt;md5&lt;/code&gt; vs &lt;code&gt;md5sum&lt;/code&gt;, BSD &lt;code&gt;find&lt;/code&gt; vs GNU &lt;code&gt;find&lt;/code&gt;, Homebrew path changes across macOS versions. Scripts written on one machine often fail on another.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single operator dependency.&lt;/strong&gt; The script works for the person who wrote it. A replacement DIT on day two of a shoot cannot debug someone else&apos;s bash.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dedicated tools earned their place here. Not by doing something scripts couldn&apos;t do in principle, but by handling concurrency, state recovery, and cross-platform consistency at a level that single-author shell scripts cannot sustain.&lt;/p&gt;
&lt;h2&gt;The implicit spec&lt;/h2&gt;
&lt;p&gt;DIT scripts contain an implicit product specification. Every line traces to a concrete incident or requirement.&lt;/p&gt;
&lt;p&gt;No product manager spec&apos;d triple-Sosumi error alerts. No UX researcher determined that the copy log should include a &lt;code&gt;df -h&lt;/code&gt; disk space snapshot at the start. No feature request asked for copy resumption after a power failure killed the truck&apos;s inverter. These features exist because a specific production incident made them necessary.&lt;/p&gt;
&lt;p&gt;The useful measure for any copy tool is whether it works under production conditions: hour fourteen, six drives, the AD asking when media will be ready for editorial. Bash scripts met this test by construction. No splash screen, no onboarding flow, no update notification, no analytics ping. Instant launch, single function, clear result.&lt;/p&gt;
&lt;p&gt;The scripts remain the clearest specification for what data management software needs to do. Not what it could do. What it needs to do.&lt;/p&gt;
</content:encoded></item><item><title>Why your Premiere conform is broken (and where the frames go)</title><link>https://datum.film/blog/premiere-conform-bugs/</link><guid isPermaLink="true">https://datum.film/blog/premiere-conform-bugs/</guid><description>Premiere Pro&apos;s FCP7 XML export has at least seven documented bugs that break conforms. 25fps becomes 50fps. 23.976 drifts to 24. Speed changes double-apply. Here is what actually goes wrong and how to survive it.</description><pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You send the XML. The colourist imports it into Resolve. They call you twenty minutes later. &quot;Your timeline is 50fps.&quot; You check the sequence settings. 1080p25, progressive, no fields. You exported correctly. You did everything right. And the conform is still broken.&lt;/p&gt;
&lt;p&gt;This is one of the most persistent problems in professional post-production. Premiere Pro&apos;s FCP7 XML export contains bugs that have been documented by colourists, conform artists, and finishing editors for over a decade. Some are fixed. Most are not. All of them cost hours.&lt;/p&gt;
&lt;p&gt;This post maps the specific failures, explains the mechanics behind each one, and describes the workarounds that working professionals actually use. If you have ever had a conform fall apart after leaving Premiere, you will recognise some of these.&lt;/p&gt;
&lt;h2&gt;The landscape of a Premiere conform&lt;/h2&gt;
&lt;p&gt;The conform is the moment where editorial decisions meet original camera media. The editor works with proxies or transcoded dailies. When the cut is locked, the timeline description travels to the colourist or finishing house, where it is matched against the high-resolution source files. The format that carries this description is usually one of three things: an EDL, an AAF, or an XML.&lt;/p&gt;
&lt;p&gt;From Premiere Pro, the standard export for grading handoff is FCP7 XML. This is Apple&apos;s XMEML format, originally designed for Final Cut Pro 7. Premiere adopted it as its primary interchange format because the ecosystem already supported it. Resolve, Baselight, Flame, Hiero, and Scratch all read it. The format handles multi-track timelines, nested sequences, speed changes, and transitions. On paper, it is the right tool for the job.&lt;/p&gt;
&lt;p&gt;In practice, Premiere&apos;s implementation of the FCP7 XML export has a cluster of bugs that affect timing, frame rates, and metadata accuracy. The bugs are not theoretical. They show up in real conforms, on real schedules, with real budgets attached.&lt;/p&gt;
&lt;h2&gt;The seven bugs&lt;/h2&gt;
&lt;p&gt;The interactive table below summarises the known issues. Click any row for a longer explanation of the mechanism.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Premiere FCP7 XML bug summary&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Field dominance on progressive: critical in Premiere 25.0 and 25.1. Progressive 25fps sequences export as interlaced and import as 50fps. Fixed in 25.2.&lt;/li&gt;
&lt;li&gt;23.976 exported as 24fps: critical. About 1 frame of drift every 42 seconds, or about 128 frames over 90 minutes. Only partially fixed.&lt;/li&gt;
&lt;li&gt;Timecode string mismatch: high severity. The &lt;code&gt;&amp;lt;string&amp;gt;&lt;/code&gt; timecode can disagree with the authoritative &lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt; count.&lt;/li&gt;
&lt;li&gt;displayformat always wrong: medium severity. Drop-frame versus non-drop-frame cannot be trusted from the standard field.&lt;/li&gt;
&lt;li&gt;Speed or conform double-apply: critical. Retimed clips can play at the wrong speed after conform.&lt;/li&gt;
&lt;li&gt;Frame rounding on retimes: high severity. One-frame disagreements accumulate across edits.&lt;/li&gt;
&lt;li&gt;Source timecode misread: high severity. Some MXF and MOV sources are read with the wrong source timecode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Three of these are critical. They do not produce subtle errors. They produce conforms that are visibly, obviously wrong in ways that halt the grading session.&lt;/p&gt;
&lt;h2&gt;Bug 1: Your 25fps timeline becomes 50fps&lt;/h2&gt;
&lt;p&gt;This is the one that generates the most support tickets. In Premiere Pro 25.0 and 25.1, exporting a progressive 25fps sequence via FCP7 XML writes the field dominance metadata incorrectly. Instead of &lt;code&gt;fielddominance=none&lt;/code&gt; (progressive), it writes &lt;code&gt;fielddominance=lower&lt;/code&gt; (interlaced, lower field first).&lt;/p&gt;
&lt;p&gt;Every downstream tool that reads this XML does the correct thing with the incorrect data. When Resolve sees &lt;code&gt;fielddominance=lower&lt;/code&gt; at 25fps, it interprets the content as 25fps interlaced, which is 50 fields per second. It creates a 50fps timeline. Baselight does the same. Flame does the same. The tools are not wrong. The XML is.&lt;/p&gt;
&lt;p&gt;The bug only affects sequences created from scratch in Premiere 25.x. If you opened a project originally created in version 24.x, the export was correct. Adobe fixed it in version 25.2 (build 10), but the fix only applies if you update and re-export. Every XML that left a 25.0 or 25.1 installation carries this error.&lt;/p&gt;
&lt;p&gt;The same bug also affects 29.97fps progressive sequences. The field dominance is written as &lt;code&gt;lower&lt;/code&gt; even when the sequence is explicitly set to &quot;Fields: No Fields (Progressive Scan).&quot;&lt;/p&gt;
&lt;p&gt;The fix is a single find-and-replace in a text editor: change &lt;code&gt;&amp;lt;fielddominance&amp;gt;lower&amp;lt;/fielddominance&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;fielddominance&amp;gt;none&amp;lt;/fielddominance&amp;gt;&lt;/code&gt;. But you need to know the bug exists before you think to look for it.&lt;/p&gt;
&lt;h2&gt;Bug 2: The silent drift at 23.976&lt;/h2&gt;
&lt;p&gt;This one is worse in some ways because it does not announce itself. It starts quiet and gets louder.&lt;/p&gt;
&lt;p&gt;In the FCP7 XML format, the difference between true 24fps and 23.976fps is encoded by a single boolean flag called &lt;code&gt;ntsc&lt;/code&gt;. When &lt;code&gt;ntsc&lt;/code&gt; is TRUE, the frame rate is reduced by the familiar 0.1% NTSC factor: 24 becomes 23.976, 30 becomes 29.97, 60 becomes 59.94. When Premiere exports a 23.976fps sequence with &lt;code&gt;ntsc=FALSE&lt;/code&gt;, every downstream tool reads it as true 24fps.&lt;/p&gt;
&lt;p&gt;The rate error is 24 / 23.976 = 1.001001. That is a drift of approximately one frame every 42 seconds. On a 30-second spot, you will never notice. On a feature film, the numbers look like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;23.976 vs 24fps drift examples&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;30-second spot: less than 1 frame of drift.&lt;/li&gt;
&lt;li&gt;5-minute short: about 7 frames.&lt;/li&gt;
&lt;li&gt;22-minute episode: about 31 frames, roughly 1.3 seconds.&lt;/li&gt;
&lt;li&gt;44-minute episode: about 63 frames, roughly 2.6 seconds.&lt;/li&gt;
&lt;li&gt;90-minute feature: about 128 frames, roughly 5.3 seconds.&lt;/li&gt;
&lt;li&gt;120-minute feature: about 171 frames, roughly 7.1 seconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try selecting the 90-minute preset. By the end of a standard feature, the conform has drifted by roughly 128 frames. That is 5.3 seconds. Dialogue is completely out of sync with picture. Cuts land in the wrong place. And because the drift is gradual (linear over the programme length), the first reel might look fine during a spot check. The problem only becomes obvious in the back half, by which point the colourist may have graded 40 minutes of material against a timeline that was never correct.&lt;/p&gt;
&lt;h2&gt;Bug 5: The speed change trap&lt;/h2&gt;
&lt;p&gt;This is the hardest bug to diagnose because it only affects some clips on the timeline.&lt;/p&gt;
&lt;p&gt;When Premiere exports an XML containing clips that have been retimed (speed changed), it bakes the frame rate conform into the speed value for those clips. If a 50fps clip is conformed to 25fps (a 50% speed interpretation) and then the editor applies an 80% creative speed change, Premiere writes a speed of 40% into the XML. It multiplied 80% by the 50% conform factor.&lt;/p&gt;
&lt;p&gt;The problem: Premiere does not do this for clips that have not been retimed. Non-retimed clips export with their raw durations, and the downstream tool is expected to apply its own frame rate conform. This creates an inconsistency within a single timeline. The grading tool re-applies the conform to everything, which is correct for the non-retimed clips and wrong for the retimed ones. The retimed clips end up playing at half their intended speed.&lt;/p&gt;
&lt;p&gt;This compounds with nesting. If the retimed clip is inside a nested sequence, each nesting level can introduce its own conform factor. The result is clips playing at speeds that bear no obvious relationship to the editor&apos;s intent.&lt;/p&gt;
&lt;h2&gt;The workarounds people actually use&lt;/h2&gt;
&lt;p&gt;The community has developed a set of survival strategies, none of them elegant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Manual XML editing.&lt;/strong&gt; Open the XML in a text editor, find-and-replace the broken values. This works for the field dominance bug and the ntsc flag. It does not help with speed change interactions or frame rounding errors, because those require understanding the relationship between specific clips and their source media.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Export as EDL instead.&lt;/strong&gt; Many colourists now request EDLs from Premiere editors specifically because the EDL format is too simple to carry the bugs. An EDL is just a flat list of timecodes and reel names. No field dominance metadata. No ntsc flags. No speed effect encoding. The limitation is real: EDLs support a single video track, no speed ramps, and the CMX3600 format truncates filenames to eight characters. But for a single-track cutlist, the EDL arrives intact when the XML does not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Export as AAF.&lt;/strong&gt; The Advanced Authoring Format uses a completely different encoding and avoids most of the FCP7 XML bugs. Several professionals report that AAF exports from Premiere handle 25fps progressive content correctly where XML fails. The tradeoff is that AAF files are binary, harder to debug, larger, and not universally supported (Flame, for example, prefers XML or EDL).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flatten retimes before export.&lt;/strong&gt; For the speed/conform interaction bug, the workaround is to duplicate the sequence, render all retimed clips in place, replace them with the rendered versions, and then export. This removes all speed effects from the XML, avoiding the double-conform problem. It also destroys the editor&apos;s ability to adjust retimes after handoff, which is sometimes acceptable and sometimes not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always send a reference render.&lt;/strong&gt; This does not fix any bugs. It lets the colourist detect them. Export a low-bitrate H.264 alongside the XML. The colourist plays it against the conformed timeline and spot-checks every cut point, speed change, and in/out transition. If something does not match, they know before they start grading.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Launder through Resolve.&lt;/strong&gt; A surprisingly common workflow: import the broken Premiere XML into Resolve, let Resolve partially correct the errors during import, then re-export a new XML from Resolve. Resolve&apos;s XML writer is more spec-compliant than Premiere&apos;s. This adds a step and can introduce its own rounding errors, but it often produces a usable result.&lt;/p&gt;
&lt;h2&gt;Why the standard format is not standard&lt;/h2&gt;
&lt;p&gt;The root of the problem is that FCP7 XML is not Premiere&apos;s native format. It is a translation layer. Premiere converts its internal timeline model (which uses a tick-based time system with 254016000000 ticks per second) into Apple&apos;s frame-count-based XMEML schema. Every translation is lossy. The bugs documented above are places where the translation goes wrong.&lt;/p&gt;
&lt;p&gt;Adobe knows about these issues. The field dominance bug was fixed relatively quickly once it was reported. But bugs 3, 4, 5, and 6 have been present since at least CS6 (2012) and remain unfixed in the current stable release. The timecode string mismatch and the displayformat error are so long-standing that the community treats them as known constants rather than bugs.&lt;/p&gt;
&lt;p&gt;Adobe has introduced OTIO (OpenTimelineIO) export in the Premiere Pro beta channel. This is a promising direction, but as of early 2026 it is not in the stable release, and the beta implementation has its own metadata fidelity issues: clip names revert to original filenames on roundtrip, and tape names are lost entirely. For now, FCP7 XML with manual corrections remains the standard path.&lt;/p&gt;
&lt;h2&gt;What a proper correction layer looks like&lt;/h2&gt;
&lt;p&gt;The right solution is not to fix XMLs by hand. It is to build a parser that knows about Premiere&apos;s specific failure modes and corrects them programmatically during import.&lt;/p&gt;
&lt;p&gt;A Premiere-aware conform tool needs to do five things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Detect Premiere origin.&lt;/strong&gt; Premiere embeds proprietary attributes like &lt;code&gt;MZ.Sequence.VideoTimeDisplayFormat&lt;/code&gt; in its XMLs. If these are present, the tool activates correction mode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fix field dominance.&lt;/strong&gt; If &lt;code&gt;fielddominance&lt;/code&gt; is &lt;code&gt;lower&lt;/code&gt; or &lt;code&gt;upper&lt;/code&gt; but the timebase is 25 and the source media is progressive, force it to &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fix the ntsc flag.&lt;/strong&gt; Cross-reference the declared frame rate against the source media (via probe data). If source files are 23.976 and the XML says true 24, correct the flag.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ignore displayformat.&lt;/strong&gt; Never trust the DF/NDF indicator from Premiere. Derive it from the rate and the proprietary &lt;code&gt;MZ.Sequence.VideoTimeDisplayFormat&lt;/code&gt; code if available.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use frame counts, not strings.&lt;/strong&gt; Always compute timecodes from the &lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt; element and the rate. Never trust the &lt;code&gt;&amp;lt;string&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the approach we are building into Muster&apos;s conform engine. The goal is that a colourist who does not own Premiere, and should not need to, can import a Premiere-originated XML and get a correct timeline without manual intervention.&lt;/p&gt;
&lt;h2&gt;The broader lesson&lt;/h2&gt;
&lt;p&gt;Premiere Pro is the most widely used NLE in the world. Its XML export is the primary interchange format for tens of thousands of conform workflows every year. And it has had documented, reproducible timing bugs in that export for over a decade.&lt;/p&gt;
&lt;p&gt;This is not a Premiere-specific failing. It is a structural problem with interchange formats that are translations rather than native representations. Every NLE-to-grading-tool handoff involves a lossy conversion, and the bugs live in the gap between what the source application means and what the target application reads. The only reliable defense is a correction layer that understands both sides of the conversation.&lt;/p&gt;
&lt;p&gt;If you are an editor sending XMLs to a colourist: always include a reference render. If you are a colourist receiving XMLs from Premiere: check the field dominance, check the frame rate, and never trust the timecode strings. If you are building tools that parse these files: trust the frame counts, probe the source media, and assume the metadata is guilty until proven innocent.&lt;/p&gt;
</content:encoded></item><item><title>Why the frame is green</title><link>https://datum.film/blog/why-the-frame-is-green/</link><guid isPermaLink="true">https://datum.film/blog/why-the-frame-is-green/</guid><description>You decoded a 10-bit ProRes frame and got a wall of green. The file isn&apos;t broken. Your texture format is.</description><pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You open a 10-bit ProRes file in your custom playback tool and the viewer fills with green. Not a little green tint. Full, saturated, Kermit green. The file plays fine in QuickTime. Resolve shows it correctly. Your tool shows a green rectangle with maybe a faint ghost of detail if you squint.&lt;/p&gt;
&lt;p&gt;This is one of the most common bugs in video decoder integration, and it has a single, specific cause. The pixel values are being normalised against the wrong range.&lt;/p&gt;
&lt;h2&gt;What you&apos;re actually looking at&lt;/h2&gt;
&lt;p&gt;That green frame is not random. It&apos;s your actual image data, but every channel is being read as a value very close to zero.&lt;/p&gt;
&lt;p&gt;A 10-bit video signal uses code values from 0 to 1023. When you upload those values to a GPU texture as 16-bit unsigned normalised (R16Unorm in Metal and Vulkan, DXGI_FORMAT_R16_UNORM in DirectX), the GPU normalises against the full 16-bit range: 0 to 65535. Your brightest pixel, code value 1023, becomes 1023 / 65535, which is approximately 0.0156. Your shader sees a value of 0.016 where it expected 1.0.&lt;/p&gt;
&lt;p&gt;The result: luma is nearly zero and both chroma channels are nearly zero. And zero chroma in YCbCr space has a very specific colour.&lt;/p&gt;
&lt;h2&gt;Why zero means green&lt;/h2&gt;
&lt;p&gt;Video is almost never stored as RGB. It&apos;s stored as YCbCr, where Y is luma (brightness) and Cb/Cr are colour difference channels. The neutral point for chroma is not zero. In a normalised 0.0 to 1.0 range, neutral chroma sits at 0.5 (corresponding to code value 512 in 10-bit, or 128 in 8-bit). A Cb of 0.5 and a Cr of 0.5 means &quot;no colour.&quot; Grey. Neutral.&lt;/p&gt;
&lt;p&gt;When both Cb and Cr are at 0.0 instead of 0.5, the YCbCr-to-RGB conversion matrix produces a strong green bias. The BT.709 conversion matrix defines luma as 0.2126R + 0.7152G + 0.0722B. Green dominates the luma coefficient because human vision is most sensitive to green. When you invert that relationship with zero chroma offset, the math pushes everything toward the green primary.&lt;/p&gt;
&lt;p&gt;Specifically, with Y near zero and Cb/Cr at their minimum instead of their midpoint, the conversion produces negative R and B values (which clamp to zero) and a small positive G value. Green channel survives. Red and blue do not. The result is a green frame.&lt;/p&gt;
&lt;p&gt;This is not a coincidence or a quirk of a particular implementation. It falls directly out of the matrix maths. Any YCbCr system built on BT.601, BT.709, or BT.2020 coefficients will produce green when chroma channels are biased toward zero. The green primary always carries the most luma weight, so it&apos;s the last channel standing when everything else clips.&lt;/p&gt;
&lt;h2&gt;The R16Unorm trap&lt;/h2&gt;
&lt;p&gt;The core problem is a mismatch between the data&apos;s actual bit depth and the GPU texture format&apos;s normalisation range.&lt;/p&gt;
&lt;p&gt;When a decoder hands you a 10-bit Y plane, the values sit in the range 0 to 1023. But they&apos;re stored in 16-bit words, typically left-shifted to the top of the 16-bit range (values 0 to 65472 in steps of 64) or packed at the bottom (values 0 to 1023 with the upper 6 bits zero).&lt;/p&gt;
&lt;p&gt;If the decoder packs values at the bottom of the word and you use R16Unorm, the GPU divides every value by 65535. Your 10 bits of real data occupy less than 2% of the normalised range. The image is there, technically, but it&apos;s crushed into a sliver near zero.&lt;/p&gt;
&lt;p&gt;The correct format for bottom-packed 10-bit data is R10X6Unorm (Metal&apos;s &lt;code&gt;.r16Unorm&lt;/code&gt; with manual rescaling, or Vulkan&apos;s &lt;code&gt;VK_FORMAT_R10X6_UNORM_PACK16&lt;/code&gt;). This format tells the GPU that only the top 10 bits are significant, so it normalises against 1023 instead of 65535. Your peak white becomes 1.0, as it should.&lt;/p&gt;
&lt;p&gt;If your API doesn&apos;t support R10X6 natively, the fix is to multiply by 65535.0 / 1023.0 (approximately 64.0) in your shader. Some developers left-shift the data to the top of the 16-bit word before upload instead, which also works: a value of 1023 becomes 65472, which normalises to 0.9990 under R16Unorm. Close enough for display, though not bit-exact.&lt;/p&gt;
&lt;h2&gt;Why ProRes, DNxHR, and H.265 trigger this&lt;/h2&gt;
&lt;p&gt;This bug appears disproportionately with ProRes, DNxHR, and H.265 10-bit content because these are the formats most likely to deliver 10-bit planar YCbCr data to your application.&lt;/p&gt;
&lt;p&gt;ProRes always decodes to planar YCbCr. The Apple VideoToolbox decoder on macOS hands you a CVPixelBuffer with a pixel format like &lt;code&gt;kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange&lt;/code&gt; or &lt;code&gt;kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange&lt;/code&gt;. The &quot;10Bi&quot; means 10-bit biplanar. The Y and CbCr planes each use 16-bit words with the 10-bit values in the high bits.&lt;/p&gt;
&lt;p&gt;DNxHR follows a similar pattern through its decoder. H.265 10-bit (common in mirrorless camera output, screen recordings, and streaming deliveries) decodes to the same family of pixel formats.&lt;/p&gt;
&lt;p&gt;8-bit content rarely triggers this bug because R8Unorm normalises against 255, and 8-bit data fills the full 0 to 255 range. The normalisation is correct by default. It&apos;s the jump to 10-bit in a 16-bit container that creates the mismatch.&lt;/p&gt;
&lt;p&gt;This is also why the bug tends to appear when developers move from 8-bit to 10-bit support. The 8-bit path worked perfectly. The 10-bit path uses the same upload logic, same texture format, and suddenly everything is green.&lt;/p&gt;
&lt;h2&gt;The partial green variant&lt;/h2&gt;
&lt;p&gt;Sometimes you don&apos;t get a solid green frame. You get an image that&apos;s recognisable but with a heavy green cast and crushed contrast. This usually means one of three things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Only the chroma planes are wrong.&lt;/strong&gt; The Y plane is being normalised correctly (perhaps it&apos;s being handled as a different texture format or the decoder left-shifted it) but the CbCr plane is still being divided by 65535 instead of 1023. Luma looks roughly correct, so you see a dim, low-contrast, monochrome-ish image with a green push because the chroma offset is wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The UV offset is missing.&lt;/strong&gt; Even with correct normalisation, if your shader doesn&apos;t subtract 0.5 from the Cb and Cr channels before the matrix multiply, you get a colour bias. The neutral point for chroma is 0.5 in normalised space, not 0.0. Skipping the offset shifts every pixel&apos;s colour. Depending on the content, this can produce a green or magenta tint across the whole frame.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mixed bit depths across planes.&lt;/strong&gt; Some decoders output the Y plane at one bit depth and chroma at another (or with different packing). If you&apos;re treating all planes identically and they aren&apos;t identical, one set of channels will be wrong while the other is right.&lt;/p&gt;
&lt;p&gt;Each of these produces a different-looking image from the same file, and the common thread is that the pixel data is fine. The interpretation is wrong.&lt;/p&gt;
&lt;h2&gt;How to diagnose it&lt;/h2&gt;
&lt;p&gt;If you&apos;re seeing green, work through this checklist:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check your texture format.&lt;/strong&gt; Is the GPU texture format&apos;s normalisation range appropriate for the actual bit depth of the data? For 10-bit data in 16-bit words, R16Unorm is almost certainly wrong unless the data is left-shifted to fill the 16-bit range.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check the decoder&apos;s packing convention.&lt;/strong&gt; Does your decoder put 10-bit values in the high bits or the low bits of the 16-bit word? VideoToolbox on Apple platforms uses high-bit packing. FFmpeg&apos;s &lt;code&gt;p010&lt;/code&gt; format uses high-bit packing. Other decoders may differ. Read the documentation, then verify by inspecting the raw buffer values for a known test pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check your normalisation.&lt;/strong&gt; After the GPU normalises the texture sample, does a peak-white pixel produce a value near 1.0 in your shader? If it produces 0.016, you have the R16Unorm problem. Multiply by 65535.0 / 1023.0, or switch to a format that understands 10-bit data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check your UV offset.&lt;/strong&gt; After normalisation, are you subtracting 0.5 from Cb and Cr before applying the colour conversion matrix? If not, your neutral axis is wrong and everything will have a colour cast.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check your matrix coefficients.&lt;/strong&gt; Make sure the YCbCr-to-RGB matrix matches the source content. BT.709 for HD. BT.2020 for UHD. BT.601 for SD. The wrong matrix won&apos;t produce green, but it will produce subtly wrong colour that&apos;s hard to debug on top of a normalisation issue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Render a test frame.&lt;/strong&gt; Upload a known pattern (say, 10-bit values for 75% colour bars) and verify every patch. If your white patch comes out as very dark green, the normalisation is wrong. If the patches are approximately right but hue-shifted, the UV offset or matrix is wrong.&lt;/p&gt;
&lt;h2&gt;The real lesson&lt;/h2&gt;
&lt;p&gt;The green frame is actually a useful diagnostic. It tells you something very specific: your values are being normalised against a range they don&apos;t fill. Once you know that, the fix is mechanical. Match the texture format to the data&apos;s actual bit depth, or rescale in the shader.&lt;/p&gt;
&lt;p&gt;The harder bugs are the ones that produce a plausible-looking image that&apos;s subtly wrong. A frame that&apos;s slightly dim. Colours that are almost right but shifted by a degree or two on the vectorscope. Those errors come from the same family of causes (wrong normalisation, wrong offset, wrong matrix) but at a scale that doesn&apos;t trigger the obvious green alarm.&lt;/p&gt;
&lt;p&gt;When your frame is green, the pipeline is telling you exactly where to look.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ITU-R BT.709-6: Parameter values for the HDTV standards for production and international programme exchange&lt;/li&gt;
&lt;li&gt;ITU-R BT.2020: Parameter values for ultra-high definition television systems for production and international programme exchange&lt;/li&gt;
&lt;li&gt;Apple Developer Documentation: CVPixelBuffer pixel format types and biplanar YCbCr formats&lt;/li&gt;
&lt;li&gt;Vulkan Specification 1.3: VK_FORMAT_R10X6_UNORM_PACK16 and related multi-bit packed formats&lt;/li&gt;
&lt;li&gt;Metal Feature Set Tables: Pixel format capabilities for R16Unorm and packed 10-bit formats&lt;/li&gt;
&lt;li&gt;Microsoft DirectX: DXGI_FORMAT enumeration and video format support&lt;/li&gt;
&lt;li&gt;Charles Poynton, &lt;em&gt;Digital Video and HD: Algorithms and Interfaces&lt;/em&gt; (2012), chapters on YCbCr encoding and normalisation&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Testing that your scopes aren&apos;t lying</title><link>https://datum.film/blog/testing-scopes-arent-lying/</link><guid isPermaLink="true">https://datum.film/blog/testing-scopes-arent-lying/</guid><description>A GPU compute shader renders a vectorscope 100x faster than the CPU. But if the GPU gets it wrong, you have a fast liar.</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A GPU compute shader can render a 1920x1080 vectorscope in under a millisecond. The equivalent CPU implementation takes roughly 80 to 120 milliseconds, depending on the machine and the complexity of the trace. That is not a subtle difference. It is the difference between a scope that updates at video rate and one that updates like a progress bar.&lt;/p&gt;
&lt;p&gt;So you write the GPU path. You port the math to WGSL or GLSL, you dispatch a compute shader that bins every pixel into the correct chrominance coordinate, you accumulate intensity into a texture, and you ship it. The scope renders beautifully. It looks right.&lt;/p&gt;
&lt;p&gt;But &quot;looks right&quot; is doing a lot of work in that sentence.&lt;/p&gt;
&lt;h2&gt;Why the CPU path exists&lt;/h2&gt;
&lt;p&gt;Oscillio renders scopes on the GPU because it has to. Real-time video monitoring at 24fps or higher demands it. But every scope type also has a CPU implementation that produces the same output, pixel for pixel. This is not a fallback for machines without a GPU. It is the reference.&lt;/p&gt;
&lt;p&gt;The CPU path is written in straightforward Rust. No SIMD intrinsics, no threading tricks, no clever bit manipulation. It processes pixels in scanline order, applies the colour math with f64 precision, and writes into a plain &lt;code&gt;Vec&amp;lt;u32&amp;gt;&lt;/code&gt; framebuffer. It is slow. It is also unambiguous. When the CPU path says a particular input pixel maps to a particular position on the vectorscope at a particular intensity, that answer is definitionally correct, because the CPU path &lt;em&gt;is&lt;/em&gt; the spec.&lt;/p&gt;
&lt;p&gt;This might seem like a lot of code to maintain for something users never see. It is. But the alternative is worse: a GPU-only pipeline where correctness is defined by &quot;does it look right to a human squinting at the output.&quot; That is not a definition. That is a hope.&lt;/p&gt;
&lt;h2&gt;What goes wrong on the GPU&lt;/h2&gt;
&lt;p&gt;GPU compute shaders introduce three categories of precision problems that do not exist in scalar CPU code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Floating point differences.&lt;/strong&gt; GPUs typically operate in f32. Many GPU architectures implement fused multiply-add (FMA) operations that produce slightly different rounding than separate multiply and add on the CPU. For most graphics work this is irrelevant. For a scope, it means a pixel that should land at chrominance coordinate (0.312, 0.329) might land at (0.312, 0.330). One least-significant-bit of error in the bin index. Multiply that across two million pixels and you get a vectorscope that is subtly, systematically wrong. Not visibly wrong on any single frame. Wrong in a way that would mislead a colourist making precise skin tone judgments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atomic operation ordering.&lt;/strong&gt; Vectorscope rendering is fundamentally a histogram problem. Multiple pixels map to the same bin, and their intensities need to be accumulated. On the CPU, this is a simple &lt;code&gt;bins[index] += intensity&lt;/code&gt;. On the GPU, thousands of shader invocations run simultaneously, so accumulation requires atomic operations. Atomic adds on integer values are well-defined and consistent. But the order in which those atomics execute is not deterministic. If you are accumulating floating point values with atomicAdd, the result depends on execution order because floating point addition is not associative. The sum 0.1 + 0.2 + 0.3 does not necessarily equal 0.3 + 0.1 + 0.2 in IEEE 754.&lt;/p&gt;
&lt;p&gt;Oscillio sidesteps this by quantising intensity to integer values before atomic accumulation. But that quantisation introduces its own error budget that needs to be tracked and tested.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Driver-specific behaviour.&lt;/strong&gt; The same WGSL shader running on the same logical GPU can produce different output depending on the driver version. We have seen a case where an Intel Arc driver change altered the rounding behaviour of f32-to-u32 conversion inside a compute shader, shifting scope traces by one pixel on certain input values. The shader was correct. The driver was correct. The combination was different from what it was three months earlier.&lt;/p&gt;
&lt;h2&gt;Building the test harness&lt;/h2&gt;
&lt;p&gt;Testing image-producing code is not like testing a function that returns a number. You cannot write &lt;code&gt;assert_eq!(gpu_output, expected)&lt;/code&gt; because &quot;expected&quot; is a 1024x1024 texture with millions of possible correct values, and exact equality is the wrong metric anyway.&lt;/p&gt;
&lt;p&gt;Oscillio&apos;s scope test harness works in three layers.&lt;/p&gt;
&lt;h3&gt;Layer 1: Golden master comparison&lt;/h3&gt;
&lt;p&gt;For each scope type (waveform, vectorscope, histogram, CIE diagram), a set of reference input frames are rendered through the CPU path. The output textures are stored as 16-bit PNG files in the test fixtures directory. These are the golden masters.&lt;/p&gt;
&lt;p&gt;The test runs the same inputs through the GPU path and compares the output against the golden master. But &quot;compares&quot; is not pixel-exact equality. It is a per-pixel tolerance check: for each pixel, the absolute difference in each channel must be below a threshold. For Oscillio, that threshold is 2 in 8-bit space (roughly 0.8% intensity error) and 1 in the bin-index domain.&lt;/p&gt;
&lt;p&gt;When a golden master comparison fails, the harness writes three files: the expected image, the actual image, and a difference map that amplifies the error by 10x so it is visible to a human reviewer. This matters because scope precision bugs often produce differences that are invisible at 1:1 zoom but meaningful at the signal level.&lt;/p&gt;
&lt;h3&gt;Layer 2: Statistical validation&lt;/h3&gt;
&lt;p&gt;Golden master tests catch regressions, but they do not catch systematic bias. If the GPU path consistently shifts the vectorscope trace 0.5 pixels to the right, it might pass the per-pixel tolerance on every individual pixel while still being wrong in aggregate.&lt;/p&gt;
&lt;p&gt;The statistical layer computes summary metrics across the entire output: mean error, max error, error standard deviation, and the 99th percentile error. These metrics are compared against budgets defined per scope type. The vectorscope budget is tighter than the waveform budget because chrominance precision matters more than luminance precision for the use cases where people rely on vectorscopes.&lt;/p&gt;
&lt;p&gt;The harness also checks for bias. If the mean signed error (not absolute error) is consistently positive or negative, that indicates a systematic offset, not random noise. The bias threshold is set at 0.1 in 8-bit space. Anything above that triggers a failure even if every individual pixel is within tolerance.&lt;/p&gt;
&lt;h3&gt;Layer 3: Synthetic edge cases&lt;/h3&gt;
&lt;p&gt;Real video frames are poor test inputs because they exercise the common case. Most pixels in most frames produce similar chrominance values, cluster around similar luminance levels, and do not stress the boundaries of the scope&apos;s coordinate space.&lt;/p&gt;
&lt;p&gt;The synthetic test suite generates inputs designed to exercise the edges: single-pixel inputs at extreme chrominance coordinates, flat fields at specific code values, gradients that sweep through the entire gamut, patterns that force every pixel into the same histogram bin (maximum atomic contention), and frames with values at the exact boundary between adjacent bins.&lt;/p&gt;
&lt;p&gt;The single-bin-contention test is particularly valuable. It creates an input where every pixel maps to the same vectorscope coordinate. The CPU path produces a clean result because accumulation is sequential. The GPU path dispatches thousands of atomic adds to the same memory location simultaneously. If the atomic accumulation has any precision loss, it shows up here as a discrepancy between the GPU sum and the CPU sum. This test caught a bug in our original implementation where we were using atomicAdd on f32 values instead of quantised integers. The error was tiny per pixel but compounded to a visible intensity difference at high contention.&lt;/p&gt;
&lt;h2&gt;Running it&lt;/h2&gt;
&lt;p&gt;The test suite runs on CI against three GPU backends: Vulkan (Linux, discrete GPU), Metal (macOS, Apple Silicon), and DX12 (Windows, NVIDIA). Each backend gets its own tolerance budgets because driver precision varies. The Metal path is slightly more precise than the Vulkan path on the same logical operations, which we attribute to Apple&apos;s tighter shader compiler behaviour, though we have not confirmed that.&lt;/p&gt;
&lt;p&gt;Tests run on every commit that touches the scope rendering code. A full run takes about 40 seconds, most of which is GPU setup and teardown. The actual rendering and comparison is fast. The CI machines have GPUs specifically because headless CPU-only testing would not catch the bugs that matter.&lt;/p&gt;
&lt;p&gt;When a test fails, the review process is manual. A developer looks at the difference map, decides whether the error is a real bug or a legitimate precision change (like a driver update), and either fixes the shader or updates the golden master. Updating a golden master requires a comment in the commit explaining why the old reference was wrong or why the new precision characteristic is acceptable.&lt;/p&gt;
&lt;p&gt;This is deliberately not automated. The decision to accept a new precision baseline is a judgment call about signal integrity, and we do not want that decision made by a threshold.&lt;/p&gt;
&lt;h2&gt;The cost of two paths&lt;/h2&gt;
&lt;p&gt;Maintaining both CPU and GPU implementations of every scope type is real work. When the rendering algorithm changes, both paths need updating. When a new scope feature ships, it ships twice. The CPU path is typically 3x more code than the GPU path because scalar code cannot lean on implicit parallelism.&lt;/p&gt;
&lt;p&gt;The alternative is trusting the GPU output because it looks right. We tried that early on. It produced scopes that were wrong by 1 to 2 pixels on certain input values, in ways that no human reviewer caught during development, but that a colourist would eventually notice when making critical adjustments near the edges of legal gamut. A scope that is wrong at the margins is worse than no scope at all, because it teaches you to distrust the tool.&lt;/p&gt;
&lt;p&gt;The CPU reference path costs us roughly two weeks of additional engineering per scope type. The confidence it buys is worth more than that.&lt;/p&gt;
</content:encoded></item><item><title>You don&apos;t need a LUT, you need a pipeline</title><link>https://datum.film/blog/you-dont-need-a-lut/</link><guid isPermaLink="true">https://datum.film/blog/you-dont-need-a-lut/</guid><description>What &apos;colour managed&apos; actually means, why a LUT is a symptom not a solution, and how to set up OCIO without wanting to die.</description><pubDate>Mon, 09 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You downloaded a LUT pack. You dragged the .cube file onto your adjustment layer. The footage looked cinematic. You shipped it.&lt;/p&gt;
&lt;p&gt;Then you got a project that was shot on a different camera, and the LUT looked terrible. So you found another LUT. And another. And now you have a folder with 400 LUTs and a growing suspicion that there&apos;s a better way.&lt;/p&gt;
&lt;p&gt;There is. It&apos;s called colour management, and it&apos;s simpler than the OCIO documentation makes it sound.&lt;/p&gt;
&lt;h2&gt;What a LUT actually is&lt;/h2&gt;
&lt;p&gt;A LUT is a sampled snapshot of a colour transform at a specific grid resolution. It takes input RGB values and maps them to output RGB values. It knows nothing about what camera the footage came from, what display you&apos;re viewing on, or what colour space you&apos;re working in. It&apos;s a lookup table. It looks things up.&lt;/p&gt;
&lt;p&gt;When someone hands you a LUT called &quot;ARRI_LogC_to_Rec709.cube&quot;, that LUT encodes a specific conversion from a specific log encoding to a specific display standard. Change any variable (different camera, different display, different working space) and the LUT gives you the wrong answer. It&apos;s not broken. It&apos;s just answering a different question than the one you&apos;re asking.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Transfer-curve comparison for the encodings discussed in this section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is why your folder of 400 LUTs exists. Each one is a frozen answer to a specific question. A colour managed pipeline asks the question dynamically instead.&lt;/p&gt;
&lt;h2&gt;What a pipeline is&lt;/h2&gt;
&lt;p&gt;A colour managed pipeline separates the transform into steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Input transform.&lt;/strong&gt; What colour space and encoding is this footage actually in? ARRI Log-C4 with Wide Gamut 4 primaries? Sony S-Log3 with S-Gamut3.Cine? The input transform converts from the camera&apos;s encoding to a common working space.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Working space.&lt;/strong&gt; Where you do your grading. This is a defined, documented colour space that doesn&apos;t change between projects. In ACES, it&apos;s ACEScg (linear, AP1 primaries). In a custom OCIO config, it could be whatever makes sense for your facility.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Display transform.&lt;/strong&gt; What are the characteristics of the monitor you&apos;re looking at right now? Rec. 709 SDR? P3 D65? PQ for HDR? The display transform converts from your working space to whatever the display needs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each step is a defined, swappable, documented transform. Change the camera? Update the input. Change the monitor? Update the display. The working space stays the same. Your grade stays the same. That&apos;s the whole idea.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Before-and-after comparison illustrating the visual difference discussed in this section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Why LUTs still exist&lt;/h2&gt;
&lt;p&gt;LUTs aren&apos;t evil. They&apos;re useful in specific situations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;On-set monitoring.&lt;/strong&gt; A LUT on a SmallHD gives the DP an approximate look. Fast, predictable, and the monitor doesn&apos;t need OCIO installed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client review.&lt;/strong&gt; Baking a LUT into dailies gives the producer a consistent look without requiring colour management on their laptop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Final delivery.&lt;/strong&gt; Some delivery specs require baked LUTs in the output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The problem is using LUTs as your &lt;em&gt;colour management strategy&lt;/em&gt; rather than as a &lt;em&gt;delivery format&lt;/em&gt;. A LUT on your monitoring chain is fine. A LUT as the only thing standing between your camera negative and your grading software is not.&lt;/p&gt;
&lt;h2&gt;Getting started without losing your mind&lt;/h2&gt;
&lt;p&gt;The OCIO documentation assumes you already understand colour science. You don&apos;t have to. Here&apos;s the minimum viable setup:&lt;/p&gt;
&lt;h3&gt;Step 1: Pick a config&lt;/h3&gt;
&lt;p&gt;Don&apos;t build your own. Use one that already exists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ACES 1.3.&lt;/strong&gt; The industry standard. Well-documented, wide support, good default for most work. Download the OCIO config from the Academy&apos;s GitHub.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SPI-Anim / SPI-VFX.&lt;/strong&gt; Sony Pictures Imageworks configs. Battle-tested in VFX. Simpler than ACES if you don&apos;t need the full pipeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Studio config.&lt;/strong&gt; Many post houses have a house config. Ask before building your own.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 2: Tell your software where it is&lt;/h3&gt;
&lt;p&gt;Every colour-managed application needs to know where your OCIO config lives. Set the &lt;code&gt;OCIO&lt;/code&gt; environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export OCIO=/path/to/your/config.ocio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or point to it in your application&apos;s preferences. DaVinci Resolve, Nuke, and Blender all have OCIO config fields in their colour management settings.&lt;/p&gt;
&lt;h3&gt;Step 3: Set your input&lt;/h3&gt;
&lt;p&gt;When you import footage, tell the software what encoding it&apos;s in. If it&apos;s ARRI footage, the input should be ARRI LogC3 or LogC4 (depending on the camera). If it&apos;s Sony, S-Log3. If it&apos;s a VFX render, it&apos;s probably linear with whatever primaries the render engine used.&lt;/p&gt;
&lt;p&gt;This is the step that LUT workflows skip entirely. The LUT assumes it knows the input. The pipeline asks you to be explicit about it.&lt;/p&gt;
&lt;h3&gt;Step 4: Set your display&lt;/h3&gt;
&lt;p&gt;Tell the software what monitor you&apos;re looking at. If it&apos;s a standard HD monitor, that&apos;s Rec. 709. If it&apos;s a P3 display, that&apos;s DCI-P3 or Display P3. If it&apos;s HDR, that&apos;s PQ or HLG depending on the spec.&lt;/p&gt;
&lt;h3&gt;That&apos;s it&lt;/h3&gt;
&lt;p&gt;Those four things (config, environment variable, input, display) give you a colour managed pipeline. Your grades are now portable between cameras, between monitors, between facilities. You can change any variable without the image falling apart.&lt;/p&gt;
&lt;h2&gt;The folder of 400 LUTs&lt;/h2&gt;
&lt;p&gt;You don&apos;t have to delete it. But you probably won&apos;t open it much anymore. When the pipeline handles the colour management, the LUTs become what they always should have been: creative looks and delivery conversions, not structural dependencies.&lt;/p&gt;
&lt;p&gt;The day you stop thinking about which LUT to use and start thinking about which input transform matches your footage is the day colour management starts working for you instead of against you.&lt;/p&gt;
</content:encoded></item><item><title>Integer-accurate colour math</title><link>https://datum.film/blog/integer-colour-math/</link><guid isPermaLink="true">https://datum.film/blog/integer-colour-math/</guid><description>When a scope shows 940, it had better mean exactly 940. Not 939.9997 rounded up.</description><pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You&apos;ve measured a white bar. Your waveform reads 940. The question nobody asks: is that 940, or is it 939.9997 that got rounded up at the last moment before display?&lt;/p&gt;
&lt;p&gt;For creative grading, the difference is invisible. For measurement, it&apos;s the difference between a signal that passes QC and one that doesn&apos;t. Between a scope you can trust and one that&apos;s guessing.&lt;/p&gt;
&lt;p&gt;The answer depends on whether your colour math is running in floating point or fixed-point integer arithmetic. The two give different results more often than you&apos;d expect.&lt;/p&gt;
&lt;h2&gt;The BT.709 matrix&lt;/h2&gt;
&lt;p&gt;Every HD video signal encodes colour as YCbCr. The conversion from RGB is defined in ITU-R BT.709, and the matrix coefficients are specific:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Y  =  0.2126 R + 0.7152 G + 0.0722 B
Cb = -0.1146 R - 0.3854 G + 0.5000 B
Cr =  0.5000 R - 0.4542 G - 0.0458 B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are the numbers. They come from the CIE luminous efficiency function weighted for the 709 primaries. They&apos;re not negotiable and they&apos;re not approximate. The standard defines them to four decimal places.&lt;/p&gt;
&lt;p&gt;For 10-bit video, the output values are scaled into a defined range. Luma (Y) occupies code values 64 to 940. Chroma (Cb and Cr) occupies 64 to 960, with 512 representing zero. These are also exact. A full-white signal is Y = 940, not 939 or 941.&lt;/p&gt;
&lt;h2&gt;Where floating point breaks&lt;/h2&gt;
&lt;p&gt;Take a simple case: convert a 10-bit RGB triplet of (940, 940, 940) to YCbCr. This is peak white. The luma result should be exactly 940 and both chroma channels should be exactly 512.&lt;/p&gt;
&lt;p&gt;In floating point, the computation looks straightforward. Multiply each component by its coefficient, sum the results, scale to the output range, and round. But floating point doesn&apos;t represent these coefficients exactly. The number 0.7152 becomes 0.71520000000000001 in double precision (the nearest representable IEEE 754 value). That difference is on the order of 10^-17, which seems negligible. Multiply it by a 10-bit value, add it to two other similarly imprecise products, scale the result by 876 (the luma range), and the accumulated error starts to matter.&lt;/p&gt;
&lt;p&gt;The result isn&apos;t 940.0. It&apos;s 939.9999999999998 or 940.0000000000002, depending on the platform, the compiler&apos;s floating-point optimisation flags, and whether the CPU is using 80-bit extended precision internally before rounding to 64-bit. On some paths you get 940. On others you get 939 after truncation, or 941 after an unlucky rounding cascade.&lt;/p&gt;
&lt;p&gt;This isn&apos;t hypothetical. Different implementations of the same formula produce different integer results for the same input because the floating-point intermediate values aren&apos;t identical. Two scopes built by different developers, both implementing BT.709 correctly, can disagree by one code value. For a creative tool, that&apos;s irrelevant. For a measurement tool, it means one of them is wrong.&lt;/p&gt;
&lt;h2&gt;The problem compounds at the edges&lt;/h2&gt;
&lt;p&gt;The worst cases aren&apos;t peak white. They&apos;re the values near the legal limits where a single code value determines whether a signal passes or fails. A luma value of 940 is legal. A value of 941 is not. If your conversion produces 940.4999 and you round down, you get 940: legal. If the same input on a different code path produces 940.5001 and you round up, you get 941: illegal.&lt;/p&gt;
&lt;p&gt;The same problem appears at the bottom of the range. Code value 64 is legal black. Code value 63 is below legal range. A conversion that produces 64.0001 and one that produces 63.9999 will give you different answers for the same input.&lt;/p&gt;
&lt;p&gt;Chroma has the same issue. The neutral point is 512 for both Cb and Cr. A pure grey input should produce exactly 512 on both chroma channels. In floating point, you often get 511.9999 or 512.0001. After rounding, that&apos;s either 512 or 511 or 513. The error is one code value. In a creative context, nobody would ever see it. In a scope, it means your chroma trace has a DC offset on grey that shouldn&apos;t exist.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Comparator showing how different rounding choices produce different integer results.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;How integer math fixes it&lt;/h2&gt;
&lt;p&gt;The fix is to do what the hardware did: work entirely in integers. The BT.709 coefficients can be represented as ratios of integers with a fixed denominator. The standard approach uses a power-of-two denominator so the final division can be a bit shift.&lt;/p&gt;
&lt;p&gt;For example, using a 16-bit scale factor of 2^16 (65536):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0.2126 * 65536 = 13933.4336  -&amp;gt; round to 13933
0.7152 * 65536 = 46871.3472  -&amp;gt; round to 46871
0.0722 * 65536 =  4732.2192  -&amp;gt; round to 4732
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the luma calculation becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Y_scaled = 13933 * R + 46871 * G + 4732 * B
Y = (Y_scaled + 32768) &amp;gt;&amp;gt; 16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;+ 32768&lt;/code&gt; is the rounding bias. Adding half the denominator before the right shift gives you correct rounding (round-half-up) instead of truncation. Every intermediate value is an integer. Every operation is exact. There&apos;s no accumulated floating-point error because there are no floating-point operations.&lt;/p&gt;
&lt;p&gt;The key detail is choosing the integer coefficients so they sum to exactly the denominator. 13933 + 46871 + 4732 = 65536. This guarantees that equal RGB inputs produce an output that&apos;s exactly equal to the input (after scaling), because the sum of the products equals the input value times the denominator. Peak white in, peak white out. No drift.&lt;/p&gt;
&lt;p&gt;When the coefficients don&apos;t sum exactly, you adjust the largest one. Since the green coefficient has the most significant bits, nudging it by one count has the smallest relative effect. Better to be off by 1/65536 on the green coefficient than to have the entire system drift by one code value on common inputs.&lt;/p&gt;
&lt;h2&gt;The rounding bias matters&lt;/h2&gt;
&lt;p&gt;The choice of rounding strategy isn&apos;t cosmetic. Truncation (just shifting without adding the bias) introduces a systematic negative error. Every result is biased toward the lower code value. On average, you&apos;re off by half a code value, and the error is always in the same direction. A scope that truncates will read consistently low.&lt;/p&gt;
&lt;p&gt;Round-half-up (adding 0.5, or the integer equivalent of half the denominator) eliminates the systematic bias. The error on any individual sample is at most half a code value, and it&apos;s equally likely to round up or down. There&apos;s no DC offset in the error.&lt;/p&gt;
&lt;p&gt;For a waveform monitor, this distinction is the difference between a trace that sits cleanly on the graticule line and one that&apos;s consistently one pixel low. On a Tektronix hardware scope, the conversion was done in dedicated silicon that used integer math with correct rounding. The trace sat on the line. If your software scope truncates instead of rounding, your trace won&apos;t match the hardware, and the hardware was right.&lt;/p&gt;
&lt;h2&gt;The inverse matters too&lt;/h2&gt;
&lt;p&gt;Conversion is bidirectional. YCbCr comes in from the video signal, gets converted to RGB for display and analysis, then possibly back to YCbCr for output. Each conversion is an opportunity for error. If the forward conversion uses integer math and the inverse uses floating point (or vice versa), the round-trip isn&apos;t lossless even when mathematically it should be.&lt;/p&gt;
&lt;p&gt;This is why Oscillio runs the entire signal path in integer arithmetic. The YCbCr-to-RGB conversion, the measurement calculations, the histogram binning, the trace rendering coordinates. Every operation that touches a code value uses integer math with explicit rounding control. A value that enters the pipeline as 940 is still 940 when it reaches the display, not 939 and not 941.&lt;/p&gt;
&lt;p&gt;The alternative is to hope that the floating-point path through your particular compiler on your particular CPU produces the right answer for every possible input. Hope is not a measurement strategy.&lt;/p&gt;
&lt;h2&gt;Why this affects you&lt;/h2&gt;
&lt;p&gt;If you&apos;re building a creative tool (a colour grading panel, a preview window, a thumbnail generator) none of this matters. The visual difference between Y = 940 and Y = 939 is zero. Nobody can see it. Use whatever math is most convenient.&lt;/p&gt;
&lt;p&gt;If you&apos;re building a measurement tool, or using one, it matters completely. A waveform monitor exists to tell you the exact code values in your signal. If the monitor&apos;s internal math introduces a one-code-value error, the monitor is reporting a signal that isn&apos;t the signal you sent it. It&apos;s a measurement instrument that doesn&apos;t measure accurately. The fact that the error is small doesn&apos;t make it acceptable. Voltmeters don&apos;t get to be off by one millivolt because the difference is small.&lt;/p&gt;
&lt;p&gt;The hardware scope vendors understood this. The Tektronix WFM7120 didn&apos;t use floating point for signal conversion. Neither did the Leader LV-5800. They used dedicated integer pipelines with controlled rounding, because measurement is not approximation.&lt;/p&gt;
&lt;p&gt;Software scopes that want to be taken seriously as measurement tools need to meet the same bar. When the trace says 940, the signal should be 940. Not 939.9997 rounded up. Not 940.0003 rounded down. Exactly 940, because the math that produced it was exact.&lt;/p&gt;
</content:encoded></item><item><title>The parametric log transform: one equation, every camera</title><link>https://datum.film/blog/parametric-log-transform/</link><guid isPermaLink="true">https://datum.film/blog/parametric-log-transform/</guid><description>ARRI LogC4, RED Log3G10, Sony S-Log3, Canon C-Log. They look like different formats. They&apos;re the same equation with different constants.</description><pubDate>Mon, 26 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A timeline arrives with footage from three cameras. The A-cam shot ARRI LogC4. B-cam was a Sony VENICE in S-Log3. The drone sent back RED Log3G10. Three different log encodings, three different white papers, three sets of transfer function equations that look nothing alike.&lt;/p&gt;
&lt;p&gt;Except they are alike. They are the same equation with different constants.&lt;/p&gt;
&lt;p&gt;That claim sounds reductive, but it holds up under scrutiny. Every major cinema log encoding, from every major manufacturer, is an instance of a single parametric form: a piecewise function with a logarithmic segment, a linear tail near black, and a handful of published coefficients that fully define the curve. Knowing this changes how conversions work, how new cameras get supported, and how much of &quot;camera colour science&quot; is actually just six numbers in a table.&lt;/p&gt;
&lt;h2&gt;The shape of a log curve&lt;/h2&gt;
&lt;p&gt;Every cinema log encoding does the same job. The camera sensor captures light linearly, meaning twice the photons produce twice the signal. But the human eye doesn&apos;t work that way. Perception is roughly logarithmic: the difference between 1 and 2 candles feels the same as the difference between 100 and 200. Storing linear sensor data directly would waste most of the code values on highlights that all look &quot;bright&quot; and starve the shadows where banding actually matters.&lt;/p&gt;
&lt;p&gt;A log curve redistributes code values so that each photographic stop gets a roughly equal share. That&apos;s why log footage looks flat. It&apos;s not damaged or wrong. It&apos;s been packed for efficient transport, and it needs a known transform to unpack it for viewing.&lt;/p&gt;
&lt;p&gt;The question is: what specific curve does the packing?&lt;/p&gt;
&lt;h2&gt;One equation to rule them all&lt;/h2&gt;
&lt;p&gt;OpenColorIO, the open-source colour management framework used in most professional post-production software, defines a transform called &lt;code&gt;LogCameraTransform&lt;/code&gt;. It implements what the OCIO documentation calls a &quot;parametric Lin-to-Log / Log-to-Lin&quot; model. The core equation for the log segment (above a linear cutoff) is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogValue = LogSideSlope * log10(LinSideSlope * linear + LinSideOffset) + LogSideOffset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And below the cutoff, a simple linear segment takes over:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogValue = LinearSlope * linear + LinearOffset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it. Five coefficients for the logarithmic region (&lt;code&gt;LogSideSlope&lt;/code&gt;, &lt;code&gt;LogSideOffset&lt;/code&gt;, &lt;code&gt;LinSideSlope&lt;/code&gt;, &lt;code&gt;LinSideOffset&lt;/code&gt;, and &lt;code&gt;base&lt;/code&gt;), two for the linear tail (&lt;code&gt;LinearSlope&lt;/code&gt;, &lt;code&gt;LinearOffset&lt;/code&gt;), and a cutoff point where one region hands off to the other.&lt;/p&gt;
&lt;p&gt;Every major camera manufacturer&apos;s log curve is an instance of this parametric form. Different constants, identical structure. ARRI LogC4 is this equation with one set of numbers. Sony S-Log3 is this equation with another set. They look like proprietary formats. They are published constants for a shared mathematical form.&lt;/p&gt;
&lt;h2&gt;What each coefficient does&lt;/h2&gt;
&lt;p&gt;Understanding the coefficients turns the equation from abstract notation into physical intuition.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LinSideSlope&lt;/strong&gt; scales the linear input before the logarithm. It controls the overall gain applied to the scene-linear data. A higher value stretches the input range that gets mapped into the log curve, effectively determining how many stops of dynamic range the encoding can represent. ARRI&apos;s LogC4, designed for a sensor with over 17 stops of range, uses a different LinSideSlope than Sony&apos;s S-Log3, which targets around 15 stops.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LinSideOffset&lt;/strong&gt; shifts the linear input before the log is taken. This is the term that handles what happens near zero and below. Real camera sensors have noise floors and can produce negative values (through black level subtraction). The offset ensures the argument to the logarithm stays positive, preventing the curve from heading toward negative infinity. It also controls where &quot;scene linear zero&quot; maps in the output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LogSideSlope&lt;/strong&gt; scales the output of the logarithm. It determines the steepness of the curve in the code value domain. A steeper LogSideSlope means each stop of exposure occupies more code values, giving finer gradation per stop but covering fewer total stops within the 0 to 1 output range.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LogSideOffset&lt;/strong&gt; shifts the entire log output up or down. This positions the curve vertically, controlling where middle grey lands in the code value range. Most log encodings place 18% grey somewhere between 0.38 and 0.42 in normalised code values, but each manufacturer chose slightly differently. The LogSideOffset is how they did it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LinearSlope and LinearOffset&lt;/strong&gt; define the straight-line segment near black. Below a certain exposure level, the logarithmic curve would curve too steeply, creating issues with noise amplification and quantisation. The linear segment replaces the log curve in this region with a gentle straight line. The slope and offset are chosen so that the two segments meet with matched value and matched first derivative, producing a smooth, continuous function with no visible kink.&lt;/p&gt;
&lt;h2&gt;The parameters, side by side&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Curve comparison showing how camera log encodings differ by constants in the same equation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here are the actual published constants for four widely used log encodings, expressed as OCIO &lt;code&gt;LogCameraTransform&lt;/code&gt; parameters:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;ARRI LogC4&lt;/th&gt;
&lt;th&gt;Sony S-Log3&lt;/th&gt;
&lt;th&gt;RED Log3G10&lt;/th&gt;
&lt;th&gt;Canon C-Log2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LogSideSlope&lt;/td&gt;
&lt;td&gt;0.0647954&lt;/td&gt;
&lt;td&gt;0.255958&lt;/td&gt;
&lt;td&gt;0.224282&lt;/td&gt;
&lt;td&gt;0.092864&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LogSideOffset&lt;/td&gt;
&lt;td&gt;0.5515593&lt;/td&gt;
&lt;td&gt;0.410557&lt;/td&gt;
&lt;td&gt;0.386053&lt;/td&gt;
&lt;td&gt;0.556929&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinSideSlope&lt;/td&gt;
&lt;td&gt;14.0&lt;/td&gt;
&lt;td&gt;5.26316&lt;/td&gt;
&lt;td&gt;155.975327&lt;/td&gt;
&lt;td&gt;87.099375&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinSideOffset&lt;/td&gt;
&lt;td&gt;0.0928&lt;/td&gt;
&lt;td&gt;0.01125&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinearSlope&lt;/td&gt;
&lt;td&gt;6.359295&lt;/td&gt;
&lt;td&gt;6.62194&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;4.889494&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinearOffset&lt;/td&gt;
&lt;td&gt;0.092778&lt;/td&gt;
&lt;td&gt;0.037584&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0.133337&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lin break&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;0.01125&lt;/td&gt;
&lt;td&gt;0.000064&lt;/td&gt;
&lt;td&gt;0.0001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log break&lt;/td&gt;
&lt;td&gt;0.1574&lt;/td&gt;
&lt;td&gt;0.1673&lt;/td&gt;
&lt;td&gt;0.0928&lt;/td&gt;
&lt;td&gt;0.1350&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These numbers are not secrets. ARRI publishes LogC4&apos;s specification. Sony&apos;s S-Log3 white paper includes the full transfer function. RED released Log3G10&apos;s coefficients. Canon documents C-Log2 in their camera SDKs. OCIO&apos;s built-in configs collect them all in one place, normalised to the same parametric form.&lt;/p&gt;
&lt;p&gt;Look at the table and some patterns emerge. ARRI&apos;s LogSideSlope is the smallest, reflecting LogC4&apos;s design for very high dynamic range (the curve is shallower per stop, but covers more stops). RED&apos;s LinSideSlope is enormous because Log3G10 normalises its linear input differently, using a larger scale factor before the log. Sony&apos;s LinearSlope is steeper than the others, meaning S-Log3&apos;s linear-to-log transition happens with a slightly more aggressive shadow segment.&lt;/p&gt;
&lt;p&gt;But these are differences of degree, not of kind. Every camera manufacturer solved the same optimisation problem: how to distribute a finite number of code values across a wide dynamic range in a way that minimises visible quantisation at every exposure level. They arrived at the same mathematical form and chose constants tuned to their sensor characteristics.&lt;/p&gt;
&lt;h2&gt;The linear tail&lt;/h2&gt;
&lt;p&gt;The linear segment near black deserves its own moment. It exists because of a practical problem: the logarithm of zero is negative infinity.&lt;/p&gt;
&lt;p&gt;If you trace the pure log curve down toward zero exposure, the output value drops without bound. In a real system with finite bit depth, this means the lowest exposure levels would demand an absurd number of code values. Worse, sensor noise in the deep shadows would be amplified by the steepening curve, turning subtle noise into visible banding.&lt;/p&gt;
&lt;p&gt;The solution is to define a &quot;linear break point&quot; below which the log curve switches to a straight line. The linear segment is gentler, gives the shadows enough code values to stay clean, and joins the log segment smoothly. The break point itself varies by manufacturer. ARRI&apos;s LogC4 transitions at about 0.01 in scene-linear. RED&apos;s Log3G10 breaks much lower, at 0.000064. That difference reflects a design choice about how much deep shadow detail the encoding prioritises.&lt;/p&gt;
&lt;p&gt;Getting the break point wrong (or ignoring it entirely) is a common source of errors in hand-written log transforms. The parametric model handles it automatically. Define the six coefficients and the break point, and the math guarantees continuity.&lt;/p&gt;
&lt;h2&gt;Why this matters in practice&lt;/h2&gt;
&lt;p&gt;When you understand that all these log curves share a common form, several things become clear.&lt;/p&gt;
&lt;p&gt;First, converting between log encodings is not a lookup table problem. It&apos;s an analytic inverse. You can go from S-Log3 to scene-linear and then from scene-linear to LogC4 with exact math. No sampling grid, no interpolation error, no 33-point cube approximation. The inverse of the parametric log equation is another parametric log equation. OCIO does this by default when you chain &lt;code&gt;LogCameraTransform&lt;/code&gt; operations.&lt;/p&gt;
&lt;p&gt;Second, when a new camera comes out with a new log curve, you don&apos;t need to wait for someone to ship a LUT pack. You need six numbers and a break point. If the manufacturer publishes a white paper with the transfer function (and they almost always do), you can extract the OCIO parameters directly. The curve is fully defined.&lt;/p&gt;
&lt;p&gt;Third, comparing cameras becomes less mystical. The difference between LogC4 and S-Log3 is not &quot;ARRI colour science versus Sony colour science.&quot; It&apos;s a different LinSideSlope, a different LogSideOffset, a different break point. You can see exactly where the curves diverge and by how much. The engineering is transparent. The same transparency applies to the colour space matrices that sit alongside these transfer functions.&lt;/p&gt;
&lt;p&gt;And fourth, custom log encodings for VFX pipelines or virtual production become straightforward. If your LED wall needs a specific transfer function to match the response of a particular camera, you build it by choosing six parameters, not by sculpting a LUT by eye.&lt;/p&gt;
&lt;h2&gt;They published the numbers&lt;/h2&gt;
&lt;p&gt;This is, in some ways, the most remarkable part. Camera manufacturers could have kept their log curves as opaque black boxes. Some older encodings were exactly that: undocumented curves that you had to reverse-engineer from test charts and reference images.&lt;/p&gt;
&lt;p&gt;But the major manufacturers chose to publish. ARRI&apos;s LogC4 specification is a free PDF. Sony&apos;s S-Log3 white paper walks through the math. RED&apos;s IPP2 documentation includes Log3G10&apos;s full definition. Canon, Panasonic, and Blackmagic have all followed suit. The industry converged not just on a shared mathematical form, but on a culture of documenting it openly.&lt;/p&gt;
&lt;p&gt;OCIO&apos;s &lt;code&gt;LogCameraTransform&lt;/code&gt; is the practical expression of that convergence. It says: here is the general equation, and here are the constants for every camera we know about. If your camera isn&apos;t in the list, give us the constants and it will be.&lt;/p&gt;
&lt;p&gt;The next time you see &quot;S-Log3&quot; in a dropdown menu and feel a flicker of uncertainty about what that means, remember: it&apos;s five coefficients, a linear tail, and a break point. The same five coefficients, linear tail, and break point as every other camera in the list. The equation is the same. Only the numbers change.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ARRI. &quot;ARRI LogC4 Logarithmic Color Space: Specification.&quot; ARRI Technical Documentation, 2022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sony. &quot;Technical Summary for S-Gamut3.Cine/S-Log3 and S-Gamut3/S-Log3.&quot; Sony Professional White Paper, 2014.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RED Digital Cinema. &quot;Image Processing Pipeline (IPP2).&quot; RED Technical Documentation, 2017.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Canon. &quot;Canon Log Transfer Characteristic.&quot; Canon Cinema EOS White Paper, 2020.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenColorIO Contributors. &quot;LogCameraTransform.&quot; OpenColorIO Documentation, Academy Software Foundation. https://opencolorio.readthedocs.io/&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenColorIO Contributors. &quot;Built-in Transforms: Camera Log Encodings.&quot; OCIO v2 Configuration Reference. https://opencolorio.readthedocs.io/&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selan, Jeremy. &quot;Cinematic Color: From Your Monitor to the Big Screen.&quot; VES Handbook of Visual Effects, 2012.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>The exposure tools your camera already has (and why they lie)</title><link>https://datum.film/blog/exposure-tools-that-lie/</link><guid isPermaLink="true">https://datum.film/blog/exposure-tools-that-lie/</guid><description>False colour, zebras, waveform: what each one is actually measuring and where they disagree. And why zone-based exposure tools changed everything.</description><pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You&apos;ve been told to &quot;expose to the right.&quot; You&apos;ve been told to &quot;protect your highlights.&quot; You&apos;ve been told that false colour is the best exposure tool, or that the waveform is, or that the histogram is. Everyone has an opinion, and the opinions contradict each other.&lt;/p&gt;
&lt;p&gt;Here&apos;s the thing: they&apos;re all measuring different things. And they&apos;re all lying to you, slightly, in different ways.&lt;/p&gt;
&lt;h2&gt;What zebras actually show you&lt;/h2&gt;
&lt;p&gt;Zebras are a threshold display. They show you where pixel values exceed a number you set. That&apos;s it. They don&apos;t know what the scene looks like. They don&apos;t know what&apos;s important. They draw stripes on anything that&apos;s bright enough, whether it&apos;s a specular highlight you&apos;re happy to clip or a face that&apos;s about to lose detail.&lt;/p&gt;
&lt;p&gt;The number they compare against depends on what signal they&apos;re looking at. If your camera outputs Log-C to the monitor, the zebra threshold is in log code values. If it outputs a Rec. 709 conversion, the threshold is in display-referred values. Same scene, different zebra pattern. Neither is wrong. They&apos;re measuring different things.&lt;/p&gt;
&lt;p&gt;The common gotcha: you set your zebras at 70% because someone told you that&apos;s correct skin exposure. On a 709 output, that might be true. On a Log-C output, 70% is nowhere near where skin sits. Log curves compress highlights, so the code value for a face is much lower than you&apos;d expect. You&apos;re measuring the right number in the wrong domain.&lt;/p&gt;
&lt;h2&gt;What false colour actually shows you&lt;/h2&gt;
&lt;p&gt;False colour maps brightness ranges to colours, typically blue for shadows, green for midtones, pink for skin, red for clipping. It&apos;s a more nuanced tool than zebras because it shows you the entire exposure distribution at once.&lt;/p&gt;
&lt;p&gt;But false colour has the same domain problem. Is it operating on the log signal or the display-referred signal? A face that reads as &quot;correct skin exposure&quot; in one domain might read differently in another. And the colour mapping itself varies between manufacturers. ARRI&apos;s false colour isn&apos;t the same as SmallHD&apos;s.&lt;/p&gt;
&lt;p&gt;This is the part that trips up experienced operators. You learn one false colour scale, you internalise it, and then you switch to a different monitor or a different camera and the colours don&apos;t mean what they used to. The tool looks the same. The readings have shifted. Nothing in the interface tells you why.&lt;/p&gt;
&lt;h2&gt;The waveform is better, but not enough&lt;/h2&gt;
&lt;p&gt;The waveform monitor is the closest thing you have to an honest exposure tool. It plots every pixel&apos;s brightness vertically, spread across the horizontal axis of the frame, so you can see the full distribution of the image at a glance. Shadows at the bottom, highlights at the top, midtones where they fall.&lt;/p&gt;
&lt;p&gt;It has two advantages over zebras and false colour: it shows you everything at once rather than just flagging a threshold, and it gives you a sense of spatial distribution. You can see which part of the frame is bright and which is dark.&lt;/p&gt;
&lt;p&gt;But the waveform is still domain-dependent. A waveform displaying a log signal has a compressed shape compared to the same scene displayed in 709. Skin that sits at 42% on a Log-C waveform sits at 55–60% on a 709 waveform. If you&apos;re reading log levels with 709 instincts, you&apos;ll consistently underexpose by a stop or more. You&apos;ll think you&apos;re being careful. You&apos;ll be throwing away shadow detail.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Transfer-curve comparison for the encodings discussed in this section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The curve above shows why. Log encoding redistributes code values so that shadows get more room and highlights get less. On a linear or display-referred scale, midtones sit near the middle. On a log scale, they sit lower. Same light, different number. The waveform faithfully shows you whatever signal it receives, but it doesn&apos;t tell you which signal that is.&lt;/p&gt;
&lt;h2&gt;Why zone-based tools are different&lt;/h2&gt;
&lt;p&gt;Zone-based exposure tools, inspired by Ansel Adams&apos; zone system, divide the exposure range into perceptually even steps and show you where things fall. The key insight is that they&apos;re anchored to the scene, not the encoding.&lt;/p&gt;
&lt;p&gt;Adams divided the world into eleven zones, from pure black to pure white, with each zone representing one stop of light. Zone V was middle grey. A face was Zone VI. Bright clouds were Zone VII or VIII. The system was designed around the physics of light, not the characteristics of any particular film stock.&lt;/p&gt;
&lt;p&gt;Modern zone-based tools apply the same logic to digital cinema. They map the camera&apos;s sensor data into perceptual zones, actual stops of light above and below middle grey, so that a face always reads as a face, regardless of whether you&apos;re monitoring in Log-C, S-Log3, or Rec. 709. The display domain becomes irrelevant because the tool has already translated the signal into scene-referred stops.&lt;/p&gt;
&lt;p&gt;This is a genuine step forward. You&apos;re no longer reading a number that means different things depending on your monitoring chain. You&apos;re reading a measurement that corresponds to the light in front of the lens. If something reads as two stops over middle grey, it&apos;s two stops over middle grey, on any camera, in any log format, through any monitoring path.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Before-and-after comparison illustrating the visual difference discussed in this section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The practical upshot&lt;/h2&gt;
&lt;p&gt;You don&apos;t need to throw away your waveform or your false colour. They&apos;re useful tools. But you need to know which domain they&apos;re operating in, and you need to compensate for it. If you&apos;re monitoring in log, learn where skin sits on a log waveform instead of guessing from 709 experience. If you&apos;re using false colour, know whose false colour scale you&apos;re reading and what signal it&apos;s mapped to.&lt;/p&gt;
&lt;p&gt;And if you have access to a zone-based exposure tool, use it. Not because it&apos;s fancier, but because it removes an entire category of error. You stop asking &quot;what code value is this pixel?&quot; and start asking &quot;how many stops of light is this?&quot; The first question depends on your encoding. The second question depends on the scene. The scene is what you&apos;re trying to photograph.&lt;/p&gt;
</content:encoded></item><item><title>Same file, four apps, four different images</title><link>https://datum.film/blog/every-tool-different-image/</link><guid isPermaLink="true">https://datum.film/blog/every-tool-different-image/</guid><description>Open the same ProRes in Resolve, Premiere, FCPX, and VLC. Four different images. None wrong. All different. The disagreement is about colour interpretation, and it is predictable.</description><pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Open a single ProRes 422 HQ file tagged &lt;code&gt;1-1-1&lt;/code&gt; (BT.709 primaries, BT.709 transfer, BT.709 matrix) in four applications. DaVinci Resolve shows saturated, punchy colour. Final Cut Pro looks brighter, with lifted shadows. Premiere Pro looks like one or the other depending on a checkbox. VLC shows something else entirely.&lt;/p&gt;
&lt;p&gt;The pixels have not changed. The decode is identical. The four applications disagree about what the decoded values mean on the way to your display.&lt;/p&gt;
&lt;p&gt;This is not a format or decode problem. Every application is reading the file correctly. The disagreement is specifically about colour interpretation: gamma curves, gamut mapping, and whether to honour the display&apos;s ICC profile. That disagreement is predictable once you know what each application actually does.&lt;/p&gt;
&lt;h2&gt;What the file declares&lt;/h2&gt;
&lt;p&gt;Every QuickTime and MP4 file can carry colour metadata in its &lt;code&gt;colr&lt;/code&gt; atom. The Apple variant is &lt;code&gt;nclc&lt;/code&gt; (nonconstant luminance coding). The ISO variant is &lt;code&gt;nclx&lt;/code&gt;. They encode three integers and, for &lt;code&gt;nclx&lt;/code&gt;, a flag:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Colour primaries&lt;/strong&gt; (which red, green, and blue define the gamut)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transfer characteristics&lt;/strong&gt; (the gamma curve or OETF applied during encoding)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Matrix coefficients&lt;/strong&gt; (how RGB was converted to YCbCr)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full range flag&lt;/strong&gt; (&lt;code&gt;nclx&lt;/code&gt; only: code values 0-255, or legal range 16-235)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These values are enumerated in ISO/IEC 23001-8 (also ITU-T H.273). The same metadata can appear inside the video bitstream itself, in the VUI (Video Usability Information) of H.264 and H.265 streams. ProRes stores its own copy in the elementary stream frame header.&lt;/p&gt;
&lt;p&gt;The metadata is not ambiguous. &lt;code&gt;1-1-1&lt;/code&gt; means BT.709 primaries, BT.709 transfer, BT.709 matrix. &lt;code&gt;9-16-9&lt;/code&gt; means BT.2020 primaries, PQ transfer, BT.2020 non-constant luminance matrix. The values are defined. The disagreement between applications is not about what the file says. It is about what they do with that information on the way to the screen.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Side-by-side comparison of how different tools interpret the same tagged image.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Four applications, four rendering pipelines&lt;/h2&gt;
&lt;h3&gt;Resolve: bypass by default&lt;/h3&gt;
&lt;p&gt;DaVinci Resolve in its default colour science mode (DaVinci YRGB, colour management off) does not transform pixel values from the file&apos;s colour space to the display&apos;s colour space. It sends decoded values straight to the GPU. On a Mac with a Display P3 panel, BT.709 code values land on P3 primaries without gamut mapping. Reds and greens are visibly oversaturated compared to a BT.709 reference monitor.&lt;/p&gt;
&lt;p&gt;This is deliberate. Resolve bypasses macOS ColorSync entirely. Blackmagic&apos;s assumption: anyone doing serious colour work has a calibrated reference monitor on SDI through a DeckLink or UltraStudio, where the output path sends exact code values and the monitor handles its own calibration. The GUI viewer is a preview, not a reference.&lt;/p&gt;
&lt;p&gt;Switch to DaVinci YRGB Color Managed mode and the behaviour changes completely. Resolve reads the &lt;code&gt;colr&lt;/code&gt; atom, assigns an input colour space per clip, transforms through a wide-gamut working space (DaVinci Wide Gamut / Intermediate), and outputs to the chosen delivery space. This is a real transform-graph pipeline with explicit intent at every stage. But it is off by default in new projects, and a substantial number of Resolve users never enable it.&lt;/p&gt;
&lt;h3&gt;Final Cut Pro: full ColorSync, gamma 1.96&lt;/h3&gt;
&lt;p&gt;Final Cut Pro is fully colour-managed. It reads the file&apos;s &lt;code&gt;colr&lt;/code&gt; atom, passes the tagged colour space to macOS ColorSync, and ColorSync transforms the image to the display&apos;s ICC profile. On any Mac display manufactured since roughly 2015, that profile is Display P3 with a D65 white point.&lt;/p&gt;
&lt;p&gt;The critical detail is the transfer function. Apple&apos;s ColorSync applies a display gamma of approximately 1.96 to BT.709 content. Not 2.4 (BT.1886, the broadcast reference standard). Not 2.2 (sRGB&apos;s effective gamma). The value 1.96 descends from the original Macintosh system gamma of 1.8, adjusted upward for modern panels. The practical result: BT.709 content in Final Cut Pro renders brighter and lower-contrast than the same file on a BT.1886 reference monitor. Shadows lift. Midtones open up. The image looks &quot;washed out&quot; to anyone expecting broadcast contrast.&lt;/p&gt;
&lt;p&gt;This is a deliberate rendering decision for bright-room viewing conditions on a consumer display. It means Final Cut&apos;s viewer will never match a grading suite. A colourist who grades on a reference monitor calibrated to BT.1886 and exports a QuickTime will hear &quot;it looks washed out&quot; from every client reviewing on a Mac.&lt;/p&gt;
&lt;h3&gt;Premiere Pro: two modes, two images&lt;/h3&gt;
&lt;p&gt;Premiere Pro 25.x performs automatic colour space conversion, transforming each source clip from its detected colour space into the sequence&apos;s working space. The default SDR working space is BT.709 with 2.4 gamma.&lt;/p&gt;
&lt;p&gt;The complication is the Display Color Management toggle in Preferences &amp;gt; General. When enabled, Premiere compensates for the display&apos;s ICC profile via ColorSync, matching Final Cut&apos;s rendering path. When disabled, it sends values to the GPU without display compensation. On a P3 Mac, disabled means the same oversaturation as Resolve&apos;s default. Enabled means the gamma 1.96 lift, matching Final Cut.&lt;/p&gt;
&lt;p&gt;Same file. Same application. Two different images. The toggle is off by default and buried deep enough that many editors have never seen it.&lt;/p&gt;
&lt;p&gt;Premiere also has a history of omitting NCLC tags from exported QuickTime files. Without the tag, QuickTime Player falls back to default assumptions and applies its own gamma transform. The colourist&apos;s export looks different from the editor&apos;s export. Not because of the grade. Because of missing metadata.&lt;/p&gt;
&lt;h3&gt;VLC: minimal interpretation&lt;/h3&gt;
&lt;p&gt;VLC reads BT.709 and BT.2020 tags and can perform rudimentary colour space conversion, but it does not honour the display&apos;s ICC profile. It does not transform to the monitor&apos;s actual gamut.&lt;/p&gt;
&lt;p&gt;On a P3 Mac, VLC shows more saturated colours than Final Cut because it is not mapping BT.709 content back to the intended gamut. It shows darker midtones than Final Cut because it does not apply the Apple gamma 1.96 lift. Against Resolve&apos;s default viewer, VLC looks similar but not identical: both bypass system colour management, but their decoders handle the BT.709 transfer function with slightly different precision.&lt;/p&gt;
&lt;p&gt;VLC is showing the data with almost no interpretation applied. That is useful diagnostic information. It is not a reference.&lt;/p&gt;
&lt;h2&gt;Why the gamma 1.96 problem persists&lt;/h2&gt;
&lt;p&gt;The single most common colour complaint in post-production: a colourist grades on a reference monitor calibrated to BT.1886 (gamma 2.4), exports a QuickTime, and the client says it looks washed out. The colourist&apos;s monitor showed one thing. The client&apos;s Mac showed another. Neither is lying.&lt;/p&gt;
&lt;p&gt;The mechanism is specific. A file tagged &lt;code&gt;1-1-1&lt;/code&gt; (BT.709 primaries, BT.709 transfer, BT.709 matrix) triggers Apple&apos;s ColorSync gamma 1.96 rendering path. Blacks lift to not-quite-black. Contrast softens. The pixel values are unchanged. The display rendering is lighter than what the colourist approved on a 2.4 monitor.&lt;/p&gt;
&lt;p&gt;Two workarounds exist. Resolve can export with &quot;Rec 709-A&quot; gamma tags, which signal a 1/1.961 transfer function and avoid triggering the ColorSync lift. The BBC&apos;s open-source &lt;code&gt;qtff-parameter-editor&lt;/code&gt; rewrites NCLC tags in QuickTime files after export. Both are workarounds for a fundamental disagreement: Apple and the broadcast world use different gamma models for BT.709 content on consumer displays, and neither is planning to change.&lt;/p&gt;
&lt;h2&gt;The range trap&lt;/h2&gt;
&lt;p&gt;On top of gamma and gamut disagreements sits the full-range vs. limited-range question. BT.709 video uses &quot;legal&quot; range: code values 16-235 for 8-bit luma, with 0-15 and 236-255 reserved for headroom and footroom. Full range uses 0-255. The &lt;code&gt;nclx&lt;/code&gt; atom has a &lt;code&gt;full_range_flag&lt;/code&gt; to signal which encoding was used. Many encoders set it incorrectly. Many decoders ignore it.&lt;/p&gt;
&lt;p&gt;The symptoms are specific. Limited-range data treated as full-range: the image looks washed out, blacks sit at dark grey (code value 16 rendered as 16/255 instead of 0), whites never reach peak white. Full-range data treated as limited-range: shadows crush to black, highlights clip. This is the single most common cause of &quot;my video looks flat.&quot;&lt;/p&gt;
&lt;p&gt;Range errors compound with gamma and gamut mismatches. A file with wrong range AND wrong gamma AND wrong primaries is three interpretation errors stacked together. Each one makes the others harder to isolate.&lt;/p&gt;
&lt;h2&gt;What &quot;it looks right in Resolve&quot; actually means&lt;/h2&gt;
&lt;p&gt;&quot;It looks right in Resolve&quot; means: the image on Resolve&apos;s GUI viewer, bypassing system colour management, rendering to whatever display is connected, with whatever project colour science is active, looks acceptable to one person in one room.&lt;/p&gt;
&lt;p&gt;It does not describe what the file contains. It does not predict what a reference monitor would show, what the client will see in QuickTime, what the broadcast chain will do with the signal, or how the image will render in a browser on a phone.&lt;/p&gt;
&lt;p&gt;The file&apos;s colour metadata describes what the pixel values mean. The application&apos;s rendering pipeline decides how to present those values on a specific display. Those are different operations. Conflating them is the root of almost every colour disagreement in post-production.&lt;/p&gt;
&lt;h2&gt;Diagnosing which interpretation is correct&lt;/h2&gt;
&lt;p&gt;The viewer is not the diagnostic tool. The metadata is.&lt;/p&gt;
&lt;p&gt;Pull up FFprobe and read the colour tags directly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffprobe -v quiet -show_entries stream=color_primaries,color_transfer,color_space,color_range -of default=noprint_wrappers=1 input.mov
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells you what the file declares. &lt;code&gt;color_primaries=bt709&lt;/code&gt;, &lt;code&gt;color_transfer=bt709&lt;/code&gt;, &lt;code&gt;color_space=bt709&lt;/code&gt;, &lt;code&gt;color_range=tv&lt;/code&gt; (limited range). If any of those fields say &quot;unknown,&quot; the file is missing metadata and every application is guessing. That is the first thing to establish.&lt;/p&gt;
&lt;p&gt;Next, compare scopes, not pictures. A waveform monitor is not affected by the display&apos;s ICC profile, the GPU&apos;s gamut mapping, or the room lighting. It shows the code values. The viewer shows an interpretation of those values through a rendering pipeline. When scopes and viewer disagree, the scopes are correct.&lt;/p&gt;
&lt;p&gt;If the file&apos;s tags are present and correct, the question becomes: which application&apos;s rendering pipeline matches the intended viewing environment? Content graded for broadcast delivery on a BT.1886 monitor is correct at gamma 2.4. The same file viewed through ColorSync on a Mac will look different. Neither rendering is wrong. They are rendering for different display standards.&lt;/p&gt;
&lt;p&gt;The practical diagnostic sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Probe the file.&lt;/strong&gt; Confirm the &lt;code&gt;colr&lt;/code&gt; atom values (or VUI parameters for H.264/H.265). Confirm range.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check the scopes.&lt;/strong&gt; Do the code values match what the grade intended? Black at 16 (limited) or 0 (full)? Peak white at 235 or 255?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identify the target display standard.&lt;/strong&gt; BT.1886 for broadcast. ColorSync/gamma 1.96 for Mac consumer delivery. sRGB for web.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose the application whose pipeline matches.&lt;/strong&gt; Resolve with colour management off matches no standard. Final Cut matches Apple&apos;s consumer rendering. Resolve with colour management on, pointed at BT.1886 output, matches broadcast.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The file is not the problem. The rendering disagreement is the problem. And the rendering disagreement is resolvable once you stop asking &quot;which app looks right&quot; and start asking &quot;right for which display, in which viewing conditions, for which audience.&quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Charles Poynton, &lt;a href=&quot;https://poynton.ca/notes/misc/sde-nclc-vui-nclx.html&quot;&gt;sde, vui, nclc, nclx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ISO/IEC 23001-8 / ITU-T H.273, Coding-independent code points&lt;/li&gt;
&lt;li&gt;BBC, &lt;a href=&quot;https://github.com/bbc/qtff-parameter-editor&quot;&gt;qtff-parameter-editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Academy Software Foundation, &lt;a href=&quot;https://github.com/AcademySoftwareFoundation/EncodingGuidelines/blob/main/WebColorPreservation.md&quot;&gt;Web Color Preservation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Larry Jordan, &lt;a href=&quot;https://larryjordan.com/articles/caution-premiere-pro-final-cut-pro-x-and-quicktime-player-dont-show-the-same-color-the-same-way/&quot;&gt;Caution: Premiere Pro, Final Cut Pro X and QuickTime Player Don&apos;t Show the Same Color the Same Way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Frame.io, &lt;a href=&quot;https://blog.frame.io/2023/12/04/color-management-cheat-sheet-davinci-resolve/&quot;&gt;Color Management Cheat Sheet for DaVinci Resolve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CineD, &lt;a href=&quot;https://www.cined.com/quicktime-gamma-shift-bug-what-is-it-and-how-to-combat-it/&quot;&gt;Quicktime Gamma Shift Bug&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>&quot;Just send me a ProRes&quot;: a translation guide</title><link>https://datum.film/blog/just-send-me-a-prores/</link><guid isPermaLink="true">https://datum.film/blog/just-send-me-a-prores/</guid><description>What people mean vs what they need vs what the spec says. ProRes is not one thing. H.264 is not &apos;web quality.&apos; Here&apos;s what everyone&apos;s actually asking for.</description><pubDate>Mon, 17 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The email says &quot;just send me a ProRes.&quot; Which ProRes? ProRes 422? 422 HQ? 4444? 4444 XQ? ProRes RAW? At what resolution? What frame rate? What colour space? With or without an alpha channel? ProRes is not a quality setting. It is a family of codecs, and the name alone tells you almost nothing about the file.&lt;/p&gt;
&lt;p&gt;&quot;Just send me a ProRes&quot; is the delivery equivalent of &quot;make it pop.&quot; It communicates intent without precision, and the gap between intent and precision is where expensive re-deliveries happen.&lt;/p&gt;
&lt;h2&gt;The ProRes family is five (or six) codecs&lt;/h2&gt;
&lt;p&gt;ProRes isn&apos;t a codec. It&apos;s a family of codecs with dramatically different characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ProRes 422 Proxy.&lt;/strong&gt; Tiny files for offline editing. Not for delivery.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ProRes 422 LT.&lt;/strong&gt; Lighter version of 422. Sometimes acceptable for broadcast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ProRes 422.&lt;/strong&gt; The workhorse. Broadcast delivery standard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ProRes 422 HQ.&lt;/strong&gt; Higher bitrate. Master quality. What most people probably mean.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ProRes 4444.&lt;/strong&gt; Adds a fourth channel (alpha). Up to 12-bit. VFX delivery.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ProRes 4444 XQ.&lt;/strong&gt; Highest quality. 12-bit. The one nobody asks for because nobody knows it exists.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Reference table comparing ProRes variants and where they do or do not belong.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When a client says &quot;ProRes,&quot; they usually mean 422 HQ. But if you&apos;re delivering VFX plates, they might mean 4444. And if they&apos;re working on an M1 Mac and playing back in QuickTime Player, they might not realise that &quot;ProRes&quot; means something different at every quality level.&lt;/p&gt;
&lt;p&gt;The file size difference is real. A minute of 4K ProRes 422 HQ is roughly 7.5 GB. The same minute in ProRes 4444 XQ is over 20 GB. If someone asks for ProRes and you deliver 4444 XQ, you&apos;ve given them the best quality and the worst transfer time, and they&apos;ll wonder why their Dropbox is full.&lt;/p&gt;
&lt;h2&gt;&quot;H.264 is web quality&quot;: no it isn&apos;t&lt;/h2&gt;
&lt;p&gt;This might be the most persistent misconception in post-production. H.264 is a compression standard, not a quality level. It can produce a 2 Mbps stream for YouTube or a 200 Mbps master that&apos;s visually indistinguishable from ProRes. The codec is the language. The bitrate, profile, and level determine the quality.&lt;/p&gt;
&lt;p&gt;The confusion comes from where people encounter H.264. Most of the H.264 they see is heavily compressed web video: YouTube, Vimeo, streaming platforms. So H.264 becomes associated with &quot;lossy web stuff&quot; and ProRes with &quot;professional quality.&quot; But that&apos;s a usage pattern, not a technical limitation.&lt;/p&gt;
&lt;p&gt;H.265 (HEVC) adds another layer of confusion. It&apos;s roughly twice as efficient as H.264 at the same quality, but &quot;twice as efficient&quot; means the files are smaller, not that the quality is lower. A 100 Mbps HEVC file often looks as good as a 200 Mbps H.264 file. This matters enormously for camera originals: modern cameras that record in HEVC internally are producing excellent masters in half the file size.&lt;/p&gt;
&lt;p&gt;The reason professional workflows still prefer ProRes and DNxHR over H.264/H.265 is not quality; it&apos;s decode performance. H.264 and H.265 use temporal compression (they describe the difference between frames rather than each frame independently), which means scrubbing to an arbitrary frame requires decoding a chain of dependent frames first. ProRes and DNxHR are intraframe codecs where every frame stands alone, so random access is instant. For editing, that distinction matters more than any bitrate comparison.&lt;/p&gt;
&lt;h2&gt;DNxHR: the other professional codec&lt;/h2&gt;
&lt;p&gt;DNxHR is Avid&apos;s answer to ProRes. It fills exactly the same role (intraframe, high-quality, designed for editing) and comes in its own family of quality tiers: DNxHR LB, SQ, HQ, HQX, and 444. The quality tiers roughly parallel the ProRes family, and the workflow advantages are identical.&lt;/p&gt;
&lt;p&gt;The difference is ecosystem. ProRes is Apple&apos;s format and historically required macOS to encode (though that&apos;s less true now). DNxHR is Avid&apos;s format and works everywhere. If your post house is Avid-based, they&apos;ll ask for DNx. If they&apos;re on a Mac-based Premiere or Resolve pipeline, they&apos;ll ask for ProRes. The technical merits are near-identical. It&apos;s a question of which decoder their pipeline already has.&lt;/p&gt;
&lt;p&gt;When someone asks for &quot;ProRes or DNx,&quot; they&apos;re really asking for &quot;an intraframe codec I can edit natively.&quot; Give them whichever one matches their NLE.&lt;/p&gt;
&lt;h2&gt;Container vs codec: the box and the language&lt;/h2&gt;
&lt;p&gt;This is where things get tangled for people outside post. A .mov file is a container. A .mp4 file is a container. Both of them can hold H.264 video. The extension on the file tells you what kind of box it&apos;s in, not what language the video speaks.&lt;/p&gt;
&lt;p&gt;ProRes lives in .mov containers. DNxHR can live in .mov or .mxf containers. H.264 can live in .mov, .mp4, .mkv, or .ts containers. When a client says &quot;send me an MP4,&quot; they&apos;re asking for a container. When they say &quot;send me a ProRes,&quot; they&apos;re asking for a codec. When they say &quot;send me a ProRes MP4,&quot; they&apos;re asking for something that doesn&apos;t exist. ProRes doesn&apos;t go in MP4 containers.&lt;/p&gt;
&lt;p&gt;This isn&apos;t pedantry. If you send a .mov file to a system that only accepts .mp4, the fix might be as simple as re-wrapping the container without touching the video, a process that takes seconds and doesn&apos;t degrade quality. But if the recipient doesn&apos;t know the difference, they&apos;ll assume the file is broken, re-encode it at a fraction of the original quality, and blame you for the result.&lt;/p&gt;
&lt;h2&gt;The practical delivery checklist&lt;/h2&gt;
&lt;p&gt;A codec is not a specification. It is the beginning of one. &quot;Send me a ProRes&quot; fills in one parameter out of ten. The other nine are where rejections happen.&lt;/p&gt;
&lt;p&gt;When someone asks for a file and the spec is vague, every one of these parameters needs a value:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Codec and quality tier.&lt;/strong&gt; Not just &quot;ProRes,&quot; which ProRes? Not just &quot;H.264,&quot; at what bitrate? The difference between ProRes 422 and 422 HQ is roughly 50% in file size. The difference between 422 HQ and 4444 is an entirely different chroma structure. If they cannot answer, suggest ProRes 422 HQ for master delivery and H.264 at 50+ Mbps for review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Resolution.&lt;/strong&gt; &quot;4K&quot; means 3840x2160 to broadcasters and 4096x2160 to cinema people. Those 256 missing pixels will cause a DCP build to fail. Pixel aspect ratio matters too: square pixels (1:1) for HD and UHD, or something else if the project is anamorphic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Frame rate.&lt;/strong&gt; 23.976 fps and 24.000 fps are different frame rates. The 0.1% difference causes audio drift over the length of a feature. In NTSC territories, &quot;24p&quot; almost always means 23.976. In cinema (DCI), 24 means 24.000. The same ambiguity exists between 29.97 and 30, between 59.94 and 60. Never round the frame rate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Colour space.&lt;/strong&gt; Three components must all be specified: primaries (BT.709, BT.2020, DCI-P3), transfer function (BT.1886, PQ, HLG), and colour range (legal or full). A file encoded in BT.2020 PQ played back on a system expecting BT.709 will look desaturated and flat. A file tagged as legal range but encoded as full range will have crushed blacks and clipped highlights. These are exactly the kind of metadata fields nobody checks until delivery day.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Transfer function.&lt;/strong&gt; Often bundled with colour space, but it is a separate parameter. A file graded for a 2.4 gamma display (BT.1886) will look different on a system expecting PQ (ST 2084) for HDR. SDR and HDR are not flavours of the same thing. They require different mastering, different monitoring, and different metadata. If nobody specifies, Rec. 709 with BT.1886 transfer is the safest assumption for broadcast, and sRGB for web.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Audio layout.&lt;/strong&gt; A 5.1 mix delivered as six discrete mono tracks is not the same as a single 6-channel interleaved track. The receiving system may interpret discrete mono tracks as six separate stereo pairs. Channel order matters: SMPTE order for surround is L, R, C, LFE, Ls, Rs. Beyond the mix format, the delivery spec needs to state which channels carry what. A common broadcast layout puts the stereo programme mix on channels 1-2, music and effects on 3-4, dialogue stem on 5-6, and descriptive audio on 7-8.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. Sample rate and bit depth.&lt;/strong&gt; 24-bit, 48 kHz PCM is standard for professional video delivery. The spec needs to say so explicitly, because 16-bit audio still exists, 96 kHz recording sessions exist, and 48.048 kHz pulldown workflows exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. Container.&lt;/strong&gt; ProRes belongs in a QuickTime MOV container. ProRes in an MP4 container is technically possible (FFmpeg can do it) but practically broken. Many players and NLEs will not open it. .mxf for broadcast and Avid workflows. .mp4 for web distribution. Match the container to the destination, not to preference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9. Timecode.&lt;/strong&gt; Start timecode matters: 01:00:00:00 is the most common broadcast convention, but some deliverables require 10:00:00:00 or a specific programme-start time. For 29.97 and 59.94 fps content, the spec must state drop-frame or non-drop-frame. Drop-frame timecode skips frame numbers (not actual frames) at specific intervals to keep the display synchronised with wall-clock time. Non-drop-frame counts sequentially. Most US broadcast requires drop-frame. Most film post uses non-drop-frame. If the start TC is wrong, every edit note, subtitle cue, and QC report references the wrong frames.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10. Naming convention.&lt;/strong&gt; Automated ingest systems parse filenames to route content. A file named &lt;code&gt;final_v2_FINAL_really_final.mov&lt;/code&gt; will fail ingest at any facility with automation. The naming convention typically requires structured fields: programme title, season, episode, codec, frame rate, audio configuration, version, date. No spaces, no special characters, maximum length. Ask before rendering.&lt;/p&gt;
&lt;h2&gt;Five ways files get rejected&lt;/h2&gt;
&lt;p&gt;These are composites of real failures. Every one of them involved a file that was technically a valid ProRes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wrong colour space tagging.&lt;/strong&gt; A production delivered ProRes 422 HQ graded in DaVinci Resolve with BT.2020 primaries, but the export settings tagged the file as BT.709. The broadcaster&apos;s QC system flagged the colourimetry as out-of-spec because the pixel values exceeded the legal BT.709 gamut. Entire programme rejected. Turnaround: 48 hours.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wrong frame rate entirely.&lt;/strong&gt; A facility delivered a 23.976 fps programme when the broadcaster required 29.97 fps for playout. Not a re-wrap. A standards conversion. Turnaround: one week.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Swapped audio channels.&lt;/strong&gt; A post house delivered eight channels of audio with the M&amp;amp;E and dialogue stems swapped. The broadcaster&apos;s automated playout pulled the wrong channels for their international feed. Caught in QC only because someone listened.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Missing bars and tone.&lt;/strong&gt; A streaming platform&apos;s automated QC rejected a delivery because the file started at frame 1 of programme content. The spec required 60 seconds of bars and 30 seconds of black before programme start. The content itself was technically flawless. The rejection was procedural but non-negotiable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resolution rounding.&lt;/strong&gt; A production delivered 3840x2160 (UHD) when the spec called for 4096x2160 (DCI 4K). Two numbers that both get called &quot;4K.&quot; The DCP build failed. Re-render the entire programme.&lt;/p&gt;
&lt;h2&gt;Delivery specification template&lt;/h2&gt;
&lt;p&gt;Every field below has caused a rejection at some point. Fill them all in. Share the completed spec before the edit starts, not after the render finishes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DELIVERY SPECIFICATION
======================

Programme: [Title]
Episode:   [S01E01]
Version:   [TX / Textless / Trailer]

VIDEO
-----
Codec:              Apple ProRes 422 HQ
Container:          QuickTime (.mov)
Resolution:         1920x1080
Frame Rate:         25 fps (progressive)
Pixel Aspect Ratio: 1:1 (square)
Colour Primaries:   ITU-R BT.709
Transfer Function:  BT.1886 (2.4 gamma)
Colour Range:       Legal (64-940 10-bit)

AUDIO
-----
Codec:        Linear PCM (uncompressed)
Sample Rate:  48 kHz
Bit Depth:    24-bit
Layout:
  Ch 1-2:     Stereo Programme Mix
  Ch 3-4:     Music &amp;amp; Effects (M&amp;amp;E)
  Ch 5-6:     Dialogue Stem
  Ch 7-8:     Commentary / Descriptive Audio
Loudness:     -24 LUFS (EBU R128)

TIMECODE
--------
Start TC:     01:00:00:00
Frame Rate:   25 fps
Format:       Non-drop-frame (N/A for 25 fps)

STRUCTURE
---------
00:58:00:00 - 00:59:00:00   Colour Bars + Tone (-18 dBFS)
00:59:00:00 - 00:59:50:00   Black
00:59:50:00 - 01:00:00:00   Slate / Countdown
01:00:00:00                  Programme Start
[Programme End]              2 seconds black

FILE NAMING
-----------
Format:  [Title]_[Episode]_[Version]_[Codec]_[Date].mov
Example: TheShow_S01E01_TX_ProResHQ_20260315.mov
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adapt the specifics to the project. The point is not that every delivery uses these exact values. The point is that every delivery needs values in every field. Next time someone says &quot;just send me a ProRes,&quot; send them a version of this template with blanks and ask them to fill it in. It takes five minutes. A re-delivery takes two days if lucky and a week if not.&lt;/p&gt;
&lt;p&gt;The five minutes it takes to confirm these details will save the five hours it takes to re-render and re-upload when the spec was wrong. And if the answer is truly &quot;I don&apos;t care, just send me something,&quot; go with ProRes 422 HQ in a .mov, Rec. 709, with a stereo mix on tracks 1-2. That is the safe default. It is what &quot;just send me a ProRes&quot; almost always means.&lt;/p&gt;
</content:encoded></item><item><title>The QuickTime gamma problem</title><link>https://datum.film/blog/quicktime-gamma-problem/</link><guid isPermaLink="true">https://datum.film/blog/quicktime-gamma-problem/</guid><description>The same video file looked different on Mac and Windows for two decades. The cause was a 1993 design decision baked into Apple&apos;s colour management pipeline.</description><pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The symptom&lt;/h2&gt;
&lt;p&gt;A colourist grades a file on a calibrated reference monitor. Exports it. The client opens it in QuickTime Player on a Mac. The blacks are lifted, the contrast is flattened, the midtones are washed out. The same file on Windows looks correct.&lt;/p&gt;
&lt;p&gt;This was not a codec issue, a bit depth issue, or an export setting gone wrong. It was a gamma interpretation disagreement between Apple&apos;s colour management pipeline and every broadcast standard in existence. It persisted for roughly twenty years.&lt;/p&gt;
&lt;h2&gt;The mechanism&lt;/h2&gt;
&lt;p&gt;Video files carry colour metadata. In QuickTime and MP4 containers, this lives in the &lt;code&gt;colr&lt;/code&gt; atom as NCLC (nonconstant luminance coding) tags: three 16-bit integers for colour primaries, transfer characteristics, and matrix coefficients. A file tagged 1-1-1 declares BT.709 for all three, the standard encoding for HD video since 1990.&lt;/p&gt;
&lt;p&gt;When QuickTime Player on macOS encountered a 1-1-1 file, it handed the metadata to ColorSync, Apple&apos;s system-level colour management framework. ColorSync performed a transform from the file&apos;s declared colour space to the display&apos;s ICC profile. This is correct colour management. The problem was inside the transform.&lt;/p&gt;
&lt;p&gt;Apple&apos;s pipeline applied an effective display gamma of approximately 1.96 to BT.709 content. The broadcast standard for BT.709 displays is a gamma of 2.4, formalised as ITU-R BT.1886. The sRGB standard on most Windows monitors specified an effective gamma of 2.2. Apple&apos;s 1.96 was lower than both.&lt;/p&gt;
&lt;p&gt;Lower gamma means lifted midtones. Brighter, flatter, less contrasty. Every colour-managed application on macOS went through this same pipeline: QuickTime Player, Preview, Safari, Chrome, Quick Look. All exhibited the same washed-out result.&lt;/p&gt;
&lt;p&gt;On Windows, the same file played with the expected contrast. Not because Windows had better colour management, but because it had effectively none. Windows video applications sent pixel values straight to the display without a transform. On a monitor with native gamma near 2.2, this produced a result close to the broadcast standard. Correct by accident.&lt;/p&gt;
&lt;h2&gt;Where 1.96 came from&lt;/h2&gt;
&lt;p&gt;In 1993, Apple shipped ColorSync with a system gamma of 1.8. This was not arbitrary. Early Mac CRTs, built for desktop publishing, had a measurably different phosphor response from consumer televisions. The 1.8 figure reflected actual hardware and was convenient for prepress, where a lighter gamma better predicted print output.&lt;/p&gt;
&lt;p&gt;The rest of the industry used 2.2.&lt;/p&gt;
&lt;p&gt;When Apple transitioned to wider-gamut displays and a more sophisticated colour pipeline in the mid-2000s, the effective gamma for video content shifted to approximately 1.96. This sat between the legacy Mac gamma (1.8) and the sRGB standard (2.2). It was a deliberate backward-compatibility compromise, maintaining visual consistency for existing Mac users while inching toward the industry norm. The result matched neither Mac convention, nor broadcast convention, nor sRGB.&lt;/p&gt;
&lt;p&gt;The broadcast world did not care about Apple&apos;s transition. A reference monitor displays gamma 2.4. That is the standard. An operating system that intercepts the signal and applies 1.96 instead is, from the perspective of anyone delivering broadcast content, producing an incorrect image.&lt;/p&gt;
&lt;h2&gt;Workarounds&lt;/h2&gt;
&lt;p&gt;The post-production industry developed a set of workarounds, none of them satisfying.&lt;/p&gt;
&lt;p&gt;Some encoders wrote a transfer_characteristics value of 2 (&quot;unspecified&quot;) instead of 1, producing NCLC tags of 1-2-1. This caused ColorSync to skip its gamma interpretation. It was also technically incorrect: the encoder was lying about the file&apos;s colour space to avoid a correct-but-unwanted transform.&lt;/p&gt;
&lt;p&gt;DaVinci Resolve introduced a &quot;Rec 709-A&quot; gamma tag option specifically for this problem. A professional colour grading application needed a special export setting to work around operating system behaviour. That alone tells you how entrenched the issue was.&lt;/p&gt;
&lt;p&gt;The BBC published &lt;code&gt;qtff-parameter-editor&lt;/code&gt;, an open-source tool whose entire purpose was to rewrite three integers in a file header so that Apple&apos;s colour management would stop making the video look wrong.&lt;/p&gt;
&lt;h2&gt;Why it lasted twenty years&lt;/h2&gt;
&lt;p&gt;Apple&apos;s implementation was not, strictly speaking, a bug. ColorSync read the file&apos;s declared colour space, consulted the display&apos;s ICC profile, and performed a mathematically correct transform. The transform just used gamma assumptions that differed from the broadcast industry&apos;s expectations.&lt;/p&gt;
&lt;p&gt;Changing the value would break backward compatibility. Every application relying on ColorSync, every ICC profile tuned for Mac displays, every piece of content authored with Mac gamma in mind. Apple could not change a number in a lookup table without changing the visual appearance of every colour-managed image on every Mac.&lt;/p&gt;
&lt;p&gt;There was also a philosophical disagreement. Apple&apos;s position: the display profile should determine final appearance. The broadcast industry&apos;s position: video content should display with a specific, known gamma regardless of the display hardware. Both defensible. Incompatible.&lt;/p&gt;
&lt;h2&gt;The partial fix&lt;/h2&gt;
&lt;p&gt;In 2009, Mac OS X 10.6 Snow Leopard quietly changed the default system display gamma from 1.8 to 2.2. Apple did not emphasise this in the release notes. It was the most significant colour management change Apple had ever made.&lt;/p&gt;
&lt;p&gt;But system display gamma and video playback gamma are different things, managed by different parts of the pipeline. The 2.2 shift brought still images and the desktop closer to the rest of the world. Video playback remained at approximately 1.96 for years afterward. It took further macOS updates, the transition to P3 displays, and a new generation of colour management APIs before the situation improved meaningfully.&lt;/p&gt;
&lt;p&gt;Even today, QuickTime Player on macOS does not produce an image identical to a BT.1886 reference monitor. The gap is smaller than it was in 2008. It has not fully closed.&lt;/p&gt;
&lt;h2&gt;Practical implications&lt;/h2&gt;
&lt;p&gt;Files tagged with original 1-1-1 NCLC values still exist in archives, on hard drives, on YouTube, on client review platforms. Any file encoded between roughly 2001 and 2015 and tagged with standard BT.709 metadata falls in the gamma-ambiguous era. The metadata is correct. The file is correct. The playback result depends on which platform interprets it.&lt;/p&gt;
&lt;p&gt;For tools handling these files today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do not assume QuickTime Player is a reference.&lt;/strong&gt; It never was, and the gamma gap has not fully closed. Validate against a calibrated monitor or a player with explicit BT.1886 support.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preserve NCLC tags.&lt;/strong&gt; The 1-2-1 workaround was necessary in its time, but stripping transfer characteristics from files makes the ambiguity permanent. Correct tags with correct playback is better than no tags with unpredictable playback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flag gamma-era files.&lt;/strong&gt; Any workflow ingesting archival content should identify files from this period and surface the potential for gamma misinterpretation. The file is not wrong. The metadata is not wrong. But the rendered result may not match the original creative intent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The QuickTime gamma problem was not a bug. It was two correct systems, built on different assumptions about what a display should do, looking at the same numbers and seeing different images.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Charles Poynton, &quot;sde, vui, nclc, nclx.&quot; &lt;a href=&quot;https://poynton.ca/notes/misc/sde-nclc-vui-nclx.html&quot;&gt;poynton.ca&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ITU-R BT.709-6. Parameter values for the HDTV standards for production and international programme exchange.&lt;/li&gt;
&lt;li&gt;ITU-R BT.1886 (2011). Reference electro-optical transfer function for flat panel displays used in HDTV studio production.&lt;/li&gt;
&lt;li&gt;IEC 61966-2-1:1999. Default RGB colour space, sRGB.&lt;/li&gt;
&lt;li&gt;Todd Dominey, &quot;Why Are Videos Washed Out on the Mac? Exploring QuickTime Gamma Shift&quot; (2021). &lt;a href=&quot;https://blog.dominey.photography/2021/01/24/why-are-videos-washed-out-on-the-mac-exploring-quicktime-gamma-shift/&quot;&gt;blog.dominey.photography&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CineD, &quot;Quicktime Gamma Shift Bug: What Is It and How to Combat It&quot; (2024). &lt;a href=&quot;https://www.cined.com/quicktime-gamma-shift-bug-what-is-it-and-how-to-combat-it/&quot;&gt;cined.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;BBC qtff-parameter-editor. &lt;a href=&quot;https://github.com/bbc/qtff-parameter-editor&quot;&gt;github.com/bbc/qtff-parameter-editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Apple Developer Documentation, &quot;Tagging media with video color information.&quot; &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/tagging-media-with-video-color-information&quot;&gt;developer.apple.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Academy Software Foundation, Encoding Guidelines: Web Color Preservation. &lt;a href=&quot;https://github.com/AcademySoftwareFoundation/EncodingGuidelines/blob/main/WebColorPreservation.md&quot;&gt;github.com/AcademySoftwareFoundation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>How an 80-bit audio signal encodes time</title><link>https://datum.film/blog/ltc-80-bit-signal/</link><guid isPermaLink="true">https://datum.film/blog/ltc-80-bit-signal/</guid><description>LTC timecode is an FM signal recorded on tape. Decoding it while the tape shuttles backwards at 30x speed is a different problem than parsing a number.</description><pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most people think of timecode as a number. &lt;code&gt;01:04:23:12&lt;/code&gt;. Hours, minutes, seconds, frames. Parse the string, do some arithmetic, move on.&lt;/p&gt;
&lt;p&gt;But LTC (Linear Timecode) is not a string. It is an audio signal, a square wave recorded on tape or transmitted over a cable, encoding 80 bits of data per frame using a modulation scheme borrowed from telecommunications. Decoding it while a tape machine shuttles backwards at 30x play speed is a fundamentally different problem than parsing four numbers separated by colons.&lt;/p&gt;
&lt;h2&gt;Biphase mark encoding&lt;/h2&gt;
&lt;p&gt;LTC uses biphase mark coding to turn binary data into an analogue audio signal. The scheme has two rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Every bit period starts with a transition. High goes low, or low goes high. This transition is the clock edge.&lt;/li&gt;
&lt;li&gt;A binary 1 gets a second transition at the midpoint of the bit period. A binary 0 does not.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A stream of all 1s produces the highest frequency: transitions at every half-bit period. A stream of all 0s produces the lowest frequency: transitions only at bit boundaries, half the rate.&lt;/p&gt;
&lt;p&gt;At 30 fps with 80 bits per frame, the bit rate is 2,400 bits per second. All-ones gives a 2,400 Hz signal. All-zeros gives 1,200 Hz. Real timecode data produces a mix of frequencies between these two. For 25 fps (PAL/EBU), the range is 1,000 to 2,000 Hz. For 24 fps, 960 to 1,920 Hz.&lt;/p&gt;
&lt;p&gt;These frequencies sit comfortably in the middle of the audio band. This was deliberate. It means LTC can be recorded on any audio track, transmitted over any audio cable, and processed by any audio amplifier without modification. No special hardware, just sound.&lt;/p&gt;
&lt;p&gt;Two properties make biphase mark coding the right choice for this job. First, it is self-clocking. The receiver extracts the bit clock directly from the signal transitions, so tape speed variations do not require a separate clock reference. Second, it is direction-independent. The encoding can be decoded regardless of whether the tape is moving forwards or backwards.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Waveform view of biphase mark encoding for LTC.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The 80-bit frame&lt;/h2&gt;
&lt;p&gt;Each LTC frame is exactly 80 bits long. The bits are transmitted least significant bit first and are organized into four categories: time data, user bits, flag bits, and a sync word.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Time data (26 bits).&lt;/strong&gt; The time of day is stored in BCD (binary-coded decimal), split across the frame in four fields: frame number (0 to 29), seconds (0 to 59), minutes (0 to 59), hours (0 to 23). Each field is broken into a units nibble and a tens portion, interleaved with other data throughout the frame.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bits&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0-3&lt;/td&gt;
&lt;td&gt;Frame units (BCD, 0-9)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8-9&lt;/td&gt;
&lt;td&gt;Frame tens (BCD, 0-2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16-19&lt;/td&gt;
&lt;td&gt;Seconds units (BCD, 0-9)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24-26&lt;/td&gt;
&lt;td&gt;Seconds tens (BCD, 0-5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32-35&lt;/td&gt;
&lt;td&gt;Minutes units (BCD, 0-9)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40-42&lt;/td&gt;
&lt;td&gt;Minutes tens (BCD, 0-5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48-51&lt;/td&gt;
&lt;td&gt;Hours units (BCD, 0-9)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;56-57&lt;/td&gt;
&lt;td&gt;Hours tens (BCD, 0-2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;User bits (32 bits).&lt;/strong&gt; Eight groups of four bits each, interleaved between the time fields (bits 4-7, 12-15, 20-23, 28-31, 36-39, 44-47, 52-55, 60-63). These carry arbitrary data. Common uses include reel number, date, and scene/take identification. Two binary group flag bits (43 and 58) tell the receiver how to interpret them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flag bits.&lt;/strong&gt; Bit 10 is the drop-frame flag: set to 1 when using 29.97 fps with frame-number skipping. Bit 11 is the colour frame flag, used to identify the relationship between timecode and the NTSC or PAL colour frame sequence. Bit 27 is the biphase correction bit, a parity bit set so that each complete frame contains an even number of transitions. This guarantees that every frame starts with the same polarity, which simplifies decoder design.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sync word (bits 64-79).&lt;/strong&gt; The fixed 16-bit pattern &lt;code&gt;0011 1111 1111 1101&lt;/code&gt;. This pattern serves three purposes. It marks frame boundaries, allowing the decoder to find the start of each frame in a continuous stream. It is unique, meaning no valid combination of timecode data can accidentally produce the same bit sequence. And it is asymmetric: read forwards, the pattern is &lt;code&gt;0011111111111101&lt;/code&gt;; read backwards, it becomes &lt;code&gt;1011111111111100&lt;/code&gt;. This asymmetry tells the decoder which direction the tape is moving.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Map of the 80-bit LTC frame and what each field means.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Recording on tape&lt;/h2&gt;
&lt;p&gt;LTC is recorded as audio, typically on the highest-numbered track of a multitrack tape machine. The recommended level is roughly -10 dBu, about 10 dB below nominal operating level.&lt;/p&gt;
&lt;p&gt;This level is a compromise. Too hot and harmonic distortion adds spurious transitions that corrupt the biphase encoding. Hot timecode also bleeds into adjacent audio tracks through crosstalk. Too quiet and noise and tape dropouts cause decode errors. The -10 dBu sweet spot provides enough signal-to-noise ratio for reliable decoding while keeping crosstalk manageable.&lt;/p&gt;
&lt;p&gt;An empty guard track is kept between the timecode track and the nearest audio track. On a 24-track machine, timecode goes on track 24, track 23 stays empty. This was not optional. LTC sits between 1,200 and 2,400 Hz, a frequency range where crosstalk into dialogue or music is extremely audible and effectively impossible to remove in post.&lt;/p&gt;
&lt;p&gt;Professional videotape recorders solved this differently. They had a dedicated longitudinal address track, physically separate from the audio heads, with its own level and alignment optimized for timecode.&lt;/p&gt;
&lt;h2&gt;Crystal drift&lt;/h2&gt;
&lt;p&gt;Every timecode generator runs on a crystal oscillator, and every crystal drifts. The amount of drift determines how long two devices will stay in sync after being jam-synced at the start of a shoot.&lt;/p&gt;
&lt;p&gt;Crystal accuracy is measured in parts per million. Converting ppm to real-world timing at 30 fps:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Oscillator&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Daily drift&lt;/th&gt;
&lt;th&gt;Frames per 24 hours&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard quartz (XO)&lt;/td&gt;
&lt;td&gt;±20-50 ppm&lt;/td&gt;
&lt;td&gt;±1.7-4.3 s&lt;/td&gt;
&lt;td&gt;±51-130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temp-compensated (TCXO)&lt;/td&gt;
&lt;td&gt;±0.5-5 ppm&lt;/td&gt;
&lt;td&gt;±0.04-0.43 s&lt;/td&gt;
&lt;td&gt;±1.3-13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oven-controlled (OCXO)&lt;/td&gt;
&lt;td&gt;±0.01 ppm&lt;/td&gt;
&lt;td&gt;±0.0009 s&lt;/td&gt;
&lt;td&gt;~0.03&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A standard quartz oscillator at 20 ppm accumulates about 1.7 seconds of drift per day. That is 51 frames at 30 fps. A single frame of drift (33 ms) is enough to create visible lip-sync error.&lt;/p&gt;
&lt;p&gt;Professional timecode generators from Ambient and Denecke use TCXOs, achieving less than one frame of drift per 24-hour shooting day. Cheap cameras use standard quartz, where several frames of drift per hour is common.&lt;/p&gt;
&lt;p&gt;Crystal frequency is also temperature-dependent. Quartz follows a characteristic frequency-temperature curve (an inverted parabola for AT-cut crystals) that can shift by 10 to 20 ppm across the operating temperature range. Moving equipment between air-conditioned interiors and a hot exterior can produce step changes in drift rate. TCXO designs measure the crystal temperature and apply a correction factor, which is why professional generators specify their accuracy across a temperature range like ±0.5 ppm from -20 C to +60 C.&lt;/p&gt;
&lt;h2&gt;What happens at speed&lt;/h2&gt;
&lt;p&gt;Because LTC is a frequency-modulated signal, changes in tape speed directly change the signal frequency. A 2x shuttle doubles all frequencies. A 30x rewind shifts a 1,200-2,400 Hz signal to 36,000-72,000 Hz.&lt;/p&gt;
&lt;p&gt;Biphase mark encoding tolerates this well because the decoder does not look for a fixed frequency. It looks for transitions. But extreme speeds push the signal outside the passband of the decoder&apos;s input electronics. At very high forward speeds, the transitions come too fast for the comparators to track. In reverse, the same problem applies, compounded by the mechanical challenge of reading a tape moving in the wrong direction past a playback head.&lt;/p&gt;
&lt;p&gt;Most professional LTC decoders track speeds from about 1/30x to 60x nominal play speed. Within that range, the self-clocking property of biphase mark encoding handles speed variations automatically. The decoder extracts timing from the transitions themselves, not from an assumed clock rate.&lt;/p&gt;
&lt;p&gt;Reverse playback works because of the asymmetric sync word. The decoder recognizes the backwards pattern, knows the tape direction, and reverses its bit ordering. The time data still reads correctly. The biphase correction bit (bit 27) was calculated for forward playback, so some decoder implementations handle it differently in reverse, but the time values remain valid.&lt;/p&gt;
&lt;h2&gt;Dropouts and flywheel&lt;/h2&gt;
&lt;p&gt;Tape-based LTC is vulnerable to physical damage. A scratch or coating defect spanning a few millimetres of tape can destroy enough signal to make one or more frames undecodable.&lt;/p&gt;
&lt;p&gt;Professional decoders handle this with flywheel algorithms. When the decoder loses the signal, it continues counting frames based on the last known good timecode value, extrapolating forward (or backward) at the detected play speed. If the dropout is short (a frame or two), the flywheel bridges the gap invisibly. If the dropout is longer, the flywheel accumulates error and the decoder must wait for a valid sync word to re-establish position.&lt;/p&gt;
&lt;p&gt;Electrical interference is the other common failure mode. LTC occupies the same frequency range as audio content, so it picks up mains hum harmonics, RF interference, and cable crosstalk. Professional practice routes timecode cables separately from audio cables and uses balanced connections for common-mode rejection.&lt;/p&gt;
&lt;h2&gt;Jam-sync&lt;/h2&gt;
&lt;p&gt;Jam-sync is how timecode generators on different devices get aligned. A master generator provides a reference signal. Slave devices read the incoming LTC, set their internal counters to match, and then disconnect and free-run on their own crystal oscillators.&lt;/p&gt;
&lt;p&gt;This is the critical detail that non-specialists miss. Jam-sync sets a starting value. It does not create an ongoing lock. From the moment the cable is unplugged, each device&apos;s accuracy depends entirely on its own crystal. Two cameras jammed at call time will start in perfect agreement and diverge from there, frame by frame, at a rate determined by the quality of their oscillators.&lt;/p&gt;
&lt;p&gt;Professional practice for re-jamming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start of each shooting day. Non-negotiable.&lt;/li&gt;
&lt;li&gt;After any power cycle. Turning a camera off typically resets its counter.&lt;/li&gt;
&lt;li&gt;At lunch or major breaks. Every 4 to 6 hours compensates for drift on cameras with standard oscillators.&lt;/li&gt;
&lt;li&gt;After frame rate changes. Switching between 23.976 and 29.97 for slow-motion work disrupts the timecode relationship.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most common jam-sync failure is staleness. Cameras jammed at 7 AM are still shooting at 3 PM without a re-jam. Eight hours at 20 ppm produces about 0.6 seconds of drift: 17 or more frames. That is not a nuisance. It is a multi-camera sync failure.&lt;/p&gt;
&lt;p&gt;Some devices offer continuous jam (or jam-lock), where the slave constantly follows the incoming timecode. This eliminates drift but introduces a different risk: if the incoming signal has dropouts or glitches, the slave&apos;s counter glitches too. Most production sound mixers prefer one-shot jam with periodic re-jamming for this reason.&lt;/p&gt;
&lt;h2&gt;Why it matters&lt;/h2&gt;
&lt;p&gt;LTC was designed in the 1960s for a world of analogue tape machines, and it works the way analogue signals work. It is not a data format that happens to be carried on audio. It is an audio signal that happens to encode data. The encoding, the signal level, the guard tracks, the crystal tolerances, the flywheel algorithms: these are all properties of the signal, not the number. Understanding LTC means understanding the signal.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SMPTE ST 12-1:2014, &quot;Time and Control Code&quot;&lt;/li&gt;
&lt;li&gt;Phil Rees, &lt;a href=&quot;https://www.philrees.co.uk/articles/timecode.htm&quot;&gt;SMPTE/EBU Timecode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wikipedia, &lt;a href=&quot;https://en.wikipedia.org/wiki/Linear_timecode&quot;&gt;Linear timecode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Telestream, &lt;a href=&quot;https://www.telestream.net/pdfs/app-notes/Use-of-Time-Code-within-a-Broadcast-Facility-20W299260.pdf&quot;&gt;The Use of Time Code within a Broadcast Facility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bodet Time, &lt;a href=&quot;https://www.bodet-time.com/resources/blog/1967-comparative-analysis-of-oscillators-mems-vs-tcxo-vs-ocxo-vs-rubidium.html&quot;&gt;Comparative analysis of oscillators: MEMS vs TCXO vs OCXO vs Rubidium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Frame.io, &lt;a href=&quot;https://blog.frame.io/2017/07/17/timecode-and-frame-rates/&quot;&gt;Timecode and Frame Rates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Alister Chapman, &lt;a href=&quot;https://www.xdcam-user.com/2016/12/14/notes-on-timecode-and-timecode-sync-for-cinematographers/&quot;&gt;Notes on Timecode and Timecode Sync for Cinematographers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;libltc project, &lt;a href=&quot;https://x42.github.io/libltc/ltc_8h.html&quot;&gt;ltc.h File Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>WaveAgent and the beta that never ended</title><link>https://datum.film/blog/waveagent-beta-that-never-ended/</link><guid isPermaLink="true">https://datum.film/blog/waveagent-beta-that-never-ended/</guid><description>Sound Devices built a perfect little utility for production sound. It stayed in beta for eighteen years. The industry kept moving.</description><pubDate>Mon, 08 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you&apos;ve mixed production sound on any professional shoot in the last fifteen years, you&apos;ve used WaveAgent. Or you&apos;ve been told to use WaveAgent. Or you&apos;ve downloaded WaveAgent, opened it once, squinted at the interface, and gone back to doing things the hard way. It occupies a peculiar place in the production sound ecosystem: universally known, genuinely useful, and permanently unfinished.&lt;/p&gt;
&lt;p&gt;The title bar still says &quot;Beta.&quot;&lt;/p&gt;
&lt;p&gt;It said &quot;Beta&quot; in 2008. It says &quot;Beta&quot; now. The version number never climbed past 1.20. The official documentation carries a note that reads, &quot;Features and specifications will change.&quot; They did change, a few times, over a few years. Then they stopped changing. The note stayed.&lt;/p&gt;
&lt;p&gt;This is a story about a tool that did its job well, and about the specific kind of quiet failure that happens when a free utility from a hardware company meets a decade of format evolution. It&apos;s not a tragedy. It&apos;s barely even a complaint. But if you&apos;ve been in production sound long enough, you&apos;ve felt the gap that WaveAgent left when it stopped keeping up.&lt;/p&gt;
&lt;h2&gt;What it was built for&lt;/h2&gt;
&lt;p&gt;Sound Devices announced WaveAgent at NAB 2008 as a free companion utility for their 7-Series digital recorders: the 702, 702T, 722, 744T, and the newly released 788T. It was positioned as a &quot;WAV file librarian,&quot; and that term was more precise than it sounded. A librarian organises, catalogues, and retrieves. That&apos;s exactly what WaveAgent did.&lt;/p&gt;
&lt;p&gt;You could drag a folder of polyphonic Broadcast Wave files onto WaveAgent and immediately see everything a production sound mixer cares about. Timecode. Sample rate. Bit depth. Channel count. Scene and take numbers. Track names. The bext chunk. The iXML metadata that production recorders write alongside the audio. All of it, laid out in a clean list with a waveform display for visual confirmation.&lt;/p&gt;
&lt;p&gt;Beyond inspection, WaveAgent could split poly files into mono, combine mono files into poly, batch-rename files using metadata templates, and generate PDF sound reports. That last feature was the one mixers used most. At the end of a shoot day, you could produce a printed report of every file, every take, every channel, every timecode value. Hand it to the assistant editor with the drives. Done.&lt;/p&gt;
&lt;p&gt;The 788T even had a dedicated &quot;Wave Agent Control Mode&quot; that let you plug the recorder into your laptop via USB and edit metadata directly on the device. Sound Devices understood the workflow from cart to cutting room. WaveAgent was the bridge.&lt;/p&gt;
&lt;h2&gt;The honest label&lt;/h2&gt;
&lt;p&gt;Here&apos;s the thing about the beta designation: it was honest. Sound Devices never pretended WaveAgent was a supported product. The product page still describes it as &quot;free, unsupported software.&quot; That&apos;s not a disclaimer buried in the EULA. It&apos;s the second thing you read after the product name.&lt;/p&gt;
&lt;p&gt;In practice, &quot;unsupported&quot; meant something specific. It meant no guaranteed update cadence. No bug-fix SLA. No promise that the next macOS release wouldn&apos;t break it. No commitment to supporting new file formats, even the file formats that Sound Devices&apos; own recorders would start producing a few years later.&lt;/p&gt;
&lt;p&gt;The production sound community heard &quot;free&quot; and &quot;beta&quot; and interpreted it as &quot;works fine, just hasn&apos;t had the label updated.&quot; That interpretation was correct for about six years. Then the assumptions baked into the software started colliding with the reality outside it.&lt;/p&gt;
&lt;h2&gt;Version 1.20 and the long silence&lt;/h2&gt;
&lt;p&gt;The last real update shipped in July 2014. Version 1.20 added support for 32-track files and updated compatibility for Mac OS 10.9 and Windows 8.1. At the time, 32 tracks was generous. The 788T recorded 12 tracks. Most production work used 4 to 8 channels. Thirty-two was headroom.&lt;/p&gt;
&lt;p&gt;After 1.20, nothing. No 1.21. No 2.0. No announcement, no sunset notice, no blog post explaining the decision. WaveAgent just stopped receiving updates. The download page stayed up. The installation files stayed available. Sound Devices kept linking to it from their support articles. But the software itself entered a kind of stasis, frozen at the assumptions of 2014.&lt;/p&gt;
&lt;p&gt;2014 was a different world for production sound. Recorders used 16-bit and 24-bit integer encoding. Channel counts were modest. The iXML specification was stable. macOS was on Mavericks. WaveAgent&apos;s model of what a production BWF file looked like was still accurate.&lt;/p&gt;
&lt;p&gt;It wouldn&apos;t be for long.&lt;/p&gt;
&lt;h2&gt;The world moved&lt;/h2&gt;
&lt;p&gt;Three things happened, roughly in sequence, and none of them were WaveAgent&apos;s fault.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;32-bit float arrived.&lt;/strong&gt; Sound Devices themselves led this charge. The MixPre II series, released around 2017, introduced 32-bit float recording to the prosumer and professional production sound market. The 8-Series recorders (888 and Scorpio) followed. Thirty-two-bit float changes the fundamental contract of recording: there is no clipping in the traditional sense, gain staging becomes a post-production decision, and the file format uses IEEE 754 floating-point representation instead of PCM integer encoding. WaveAgent was built around integer audio. It does not support 32-bit float files. Not partially. Not with degraded display. It simply cannot open them. Sound Devices&apos; own recorders now produce files that Sound Devices&apos; own utility cannot read.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Channel counts grew.&lt;/strong&gt; The Scorpio records 32 channels across 36 tracks. The 888 handles 16 channels and 20 tracks. Modern productions routinely use layouts that would have been exotic in 2014: multiple booms, a dozen wireless lavalieres, plant mics, ambience rigs, safety tracks, multiple mix buses. WaveAgent&apos;s 32-track ceiling, generous a decade ago, is now a constraint. And beyond raw track count, the complexity of iXML metadata has evolved to cover Ambisonics channel ordering, surround configurations, and loudness measurements that WaveAgent&apos;s parser doesn&apos;t understand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;macOS broke the binary.&lt;/strong&gt; In 2019, macOS Catalina dropped support for 32-bit applications. WaveAgent was a 32-bit app. Overnight, every Mac user who upgraded lost access to the tool. Sound Devices responded not with a recompile, but with a support article: &quot;Installing Wave Agent on Mac OS Catalina and Big Sur.&quot; Similar guides followed for Monterey, Ventura, Sonoma, Sequoia, and Apple Silicon. Each one described workarounds. None of them described a new build. The application binary from 2014 persists, propped up by Gatekeeper exceptions and Rosetta 2 translation, each macOS cycle adding another layer of fragility.&lt;/p&gt;
&lt;h2&gt;The specific irony&lt;/h2&gt;
&lt;p&gt;It&apos;s worth sitting with the 32-bit float problem for a moment, because it&apos;s not just a compatibility gap. It&apos;s Sound Devices competing with themselves.&lt;/p&gt;
&lt;p&gt;When Sound Devices introduced 32-bit float recording, they marketed it extensively. Their website has explainers, FAQ pages, sample files, and technical white papers about 32-bit float. It&apos;s a genuine innovation that changed production sound practice. Mixers no longer need to ride gain as aggressively. The safety margin is effectively infinite. It&apos;s good technology.&lt;/p&gt;
&lt;p&gt;But WaveAgent, the tool Sound Devices built to help mixers manage files from Sound Devices recorders, cannot read the files that Sound Devices recorders now produce by default. The mixer who buys a Scorpio, records a full day in 32-bit float, and then sits down with WaveAgent to verify metadata and generate sound reports will discover that the tool cannot open any of their files.&lt;/p&gt;
&lt;p&gt;This isn&apos;t an obscure edge case. This is the primary use case, on the primary recording format, from the same manufacturer.&lt;/p&gt;
&lt;h2&gt;What people do instead&lt;/h2&gt;
&lt;p&gt;The honest answer is workarounds, and the workarounds are fine individually but collectively they represent a workflow that nobody designed.&lt;/p&gt;
&lt;p&gt;BWF MetaEdit, funded by the Library of Congress and maintained by MediaArea, is excellent for metadata inspection and editing. It understands bext chunks, iXML, and newer extensions. It&apos;s actively maintained and cross-platform. But it&apos;s built for archivists, and the interface reflects that heritage. It doesn&apos;t do poly splitting or audio playback. It doesn&apos;t generate sound reports.&lt;/p&gt;
&lt;p&gt;Reaper can explode a polyphonic WAV into separate tracks. Pro Tools can import poly BWF files and display channel metadata. But opening a 24-channel poly BWF in a DAW just to read its metadata is a category error. You&apos;re launching a recording studio to do the work of a librarian.&lt;/p&gt;
&lt;p&gt;MediaInfo gives you a quick technical summary. Python scripts using the soundfile library handle batch operations. Some mixers have written custom tools that do exactly what they need and nothing else. The fragmentation is functional but inelegant, and it means that a task WaveAgent handled in one window now requires three or four different tools depending on what you&apos;re trying to learn about the file.&lt;/p&gt;
&lt;p&gt;The production sound community on JWSoundGroup has been having the same conversation for years: what replaces WaveAgent? The thread titles tell the story. &quot;Wave Agent alternative for MacOS.&quot; &quot;Wave Agent ?&quot; &quot;Installing Wave Agent on Mac OS Ventura.&quot; Each one is a small expression of the same larger gap.&lt;/p&gt;
&lt;h2&gt;Hardware companies and their utilities&lt;/h2&gt;
&lt;p&gt;WaveAgent&apos;s trajectory follows a pattern familiar to anyone who&apos;s watched professional media tools over the past decade. A hardware manufacturer builds a free utility to support their product. The utility is good. People depend on it. But utilities don&apos;t generate revenue. They&apos;re a cost centre, justified by hardware sales, maintained as long as someone at the company champions them.&lt;/p&gt;
&lt;p&gt;Sound Devices is a hardware company in Reedsburg, Wisconsin. They make exceptional recorders. Their firmware updates are regular and substantial: the 8-Series gets meaningful new features with each release. That&apos;s where the engineering attention goes, and it should. Firmware makes the hardware more capable. A companion desktop utility, however useful, doesn&apos;t sell recorders.&lt;/p&gt;
&lt;p&gt;The CL-WiFi tells the same story in miniature. It was a WiFi adapter for the 788T with an iOS companion app for remote metadata entry. Sound Devices discontinued support in October 2015. The iOS app stopped working after iOS 10. When Bluetooth became the preferred wireless protocol, they built the Wingman app for the MixPre and 8-Series. The old tool died. The new tool served the new hardware.&lt;/p&gt;
&lt;p&gt;WaveAgent didn&apos;t even get a clean ending. It just stopped receiving updates, one macOS version at a time, one file format at a time, until the gap between what it understood and what the world required became too wide for affection to bridge.&lt;/p&gt;
&lt;h2&gt;The gap&lt;/h2&gt;
&lt;p&gt;What makes WaveAgent&apos;s absence felt isn&apos;t any single feature. It&apos;s the combination. Metadata inspection plus poly splitting plus audio playback plus sound reports plus batch renaming, all in one window, all designed by people who understood what a production sound mixer needs to see at the end of a twelve-hour day. No single alternative replicates that combination. The gap isn&apos;t empty. It&apos;s full of fragments. And fragments, however capable, are friction.&lt;/p&gt;
&lt;p&gt;If you recorded 24-bit integer audio on a 788T in 2012, WaveAgent is still the best tool for the job. If you recorded 32-bit float on a Scorpio this morning, WaveAgent cannot help you at all. The tool didn&apos;t break. The industry outgrew it. And the container format assumptions it was built on are now a decade out of date.&lt;/p&gt;
&lt;p&gt;The beta label, in hindsight, was the most honest thing about it. Sound Devices told us from the beginning that this was unfinished software. We just didn&apos;t expect &quot;unfinished&quot; to become the permanent state.&lt;/p&gt;
</content:encoded></item><item><title>Five timecode disasters and what they teach you</title><link>https://datum.film/blog/five-timecode-disasters/</link><guid isPermaLink="true">https://datum.film/blog/five-timecode-disasters/</guid><description>A production shoots multicam with mixed DF and NDF. The drift starts at 3.6 seconds per hour. By day two, the conform is unsalvageable.</description><pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Timecode failures don&apos;t announce themselves. They start quiet. Everything looks fine on set. The jam-sync reads back correctly. The cameras are rolling, the sound recorder is rolling, and the little numbers on every device match. Then the footage arrives in post, and the editor discovers that nothing lines up.&lt;/p&gt;
&lt;p&gt;The cost is always the same: post-production hours. The most expensive hours on any production, spent on work that should have been unnecessary.&lt;/p&gt;
&lt;p&gt;Here are five ways it goes wrong.&lt;/p&gt;
&lt;h2&gt;1. Mixed drop-frame and non-drop-frame on multicam&lt;/h2&gt;
&lt;p&gt;Three cameras are covering a live event. Two are set to drop-frame timecode at 29.97 fps. The third, rented from a different house and prepped by a different crew, is running non-drop-frame at the same frame rate. All three cameras jam-sync to the same master clock at call time. The timecode values match. Everyone confirms sync. Shooting begins.&lt;/p&gt;
&lt;p&gt;For the first 59 seconds, there is no problem. All three cameras produce identical timecode values. The divergence begins at the one-minute mark. Cameras 1 and 2, running drop-frame, skip frame numbers 00 and 01 at the start of minute one. Their count jumps from 00:00:59:29 to 00:01:00;02. Camera 3, running non-drop-frame, counts straight through: 00:00:59:29 to 00:01:00:00.&lt;/p&gt;
&lt;p&gt;Camera 3 is now two frame numbers behind the other two cameras. The actual video frames are still in real-time sync because all three cameras are running at the same 29.97 fps rate from the same jam point. But the labels on those frames disagree.&lt;/p&gt;
&lt;p&gt;The error compounds at every minute boundary except the tenth minutes. By the ten-minute mark, camera 3 is 18 frame numbers behind. By one hour, the gap is 108 frame numbers. That is 3.6 seconds of timecode discrepancy on footage that is, in reality, perfectly synchronous.&lt;/p&gt;
&lt;p&gt;When the editor imports all three angles and syncs by timecode, the NLE finds frames with matching timecode values that are actually 2 to 108 frames apart in real time, depending on how far into the shoot they fall. Cuts between camera 1 and camera 3 produce visible jumps. Lip-sync drifts progressively. And the error is non-linear, because it accumulates at minute boundaries and pauses at every tenth minute. Manual correction is not just tedious; it requires a per-minute offset table.&lt;/p&gt;
&lt;p&gt;The only reliable recovery is audio waveform sync. The timecode from the non-drop camera is useless for multi-camera alignment against the drop-frame cameras. The entire timecode workflow for that camera is discarded, and the sync is rebuilt from scratch using the audio.&lt;/p&gt;
&lt;p&gt;The lesson is verification, not trust. Every device on the shoot must be confirmed to the same DF/NDF mode during jam-sync. The frame rate can match and the timecode can read identically and the counting mode can still be wrong. It only takes one camera.&lt;/p&gt;
&lt;h2&gt;2. Free-run versus record-run confusion&lt;/h2&gt;
&lt;p&gt;A documentary shoot. Two cameras and a separate sound recorder. The sound mixer sets up a master timecode generator and jam-syncs the recorder in free-run mode, so timecode runs continuously and reflects time of day. The A camera is also free-run and jam-synced.&lt;/p&gt;
&lt;p&gt;The B camera operator, less familiar with the timecode workflow, has their camera set to record-run. In this mode, timecode only advances while the camera is recording. It starts from wherever it was last set, which might be 00:00:00:00, or might be 01:47:32:08 from the last clip of a previous shoot.&lt;/p&gt;
&lt;p&gt;The B camera&apos;s timecode now bears no relationship to the timecode on the A camera or the sound recorder. The sound recorder says 14:23:45:12 for a given moment. The B camera says 01:47:32:08 for the same moment. There is no offset that can reconcile them, because the B camera&apos;s timecode only ticks when it is recording. Every time the operator stops and starts the camera, the relationship between the B camera&apos;s timecode and real-world time changes.&lt;/p&gt;
&lt;p&gt;It gets worse. If the B camera is power-cycled or the recording is stopped and restarted from a reset state, record-run timecode can produce duplicate values. Two completely different moments in time labelled with the same timecode. This breaks not just sync but the fundamental assumption that timecode is unique within a given source.&lt;/p&gt;
&lt;p&gt;The post-production impact is total for the B camera. Multi-camera sync by timecode is impossible. Sound sync by timecode is impossible. Everything from the B camera must be re-synced by audio waveform or by visual cues like clappers.&lt;/p&gt;
&lt;p&gt;There is a subtler variant worth knowing. Even when all devices are set to free-run, confusion arises if some are set to &quot;free-run preset&quot; (timecode starts at a manually entered value and then runs from there) while others are set to &quot;free-run time-of-day&quot; (timecode follows the device&apos;s internal clock). If the preset value does not match time of day at the moment of jam, there will be a constant offset between devices. It is correctable, but only if someone documents it.&lt;/p&gt;
&lt;h2&gt;3. Crystal drift over a 12-hour shoot day&lt;/h2&gt;
&lt;p&gt;A documentary crew calls at 7 AM. Two cameras and a sound recorder jam-sync at call time. The shoot runs until 7 PM. Twelve hours, run-and-gun style. There are no breaks long enough for a re-jam, or the crew simply forgets.&lt;/p&gt;
&lt;p&gt;The cameras use standard quartz crystal oscillators, typical of mid-range cinema cameras. A standard quartz oscillator with 20 parts-per-million accuracy drifts by approximately 0.07 seconds per hour, roughly 2 frames at 30 fps.&lt;/p&gt;
&lt;p&gt;Over 12 hours, a single device can drift approximately 0.86 seconds from its jam point. That is 26 frames. But the number that matters is not the absolute drift of any one device. It is the relative drift between two devices, which depends on the difference in their crystal frequencies. If two cameras drift in opposite directions, the worst case is double: 1.7 seconds, or 51 frames.&lt;/p&gt;
&lt;p&gt;In realistic conditions, with cameras at different temperatures (one shooting exteriors in the sun, one in a cool interior), a relative drift of 5 to 10 frames over 12 hours is common. At 30 fps, 5 frames is 167 milliseconds. That is clearly visible lip-sync error. Audiences notice sync problems above about 45 milliseconds.&lt;/p&gt;
&lt;p&gt;The failure mode is insidious because it is gradual. Early footage from the morning syncs perfectly. It was just jam-synced. By mid-afternoon, dialogue is noticeably out of sync with picture. By late afternoon, it is worse. The drift is approximately linear, which means it is theoretically correctable with a time-stretch applied per clip. But detecting the drift rate, calculating the correction, and applying it to every clip in the timeline is hours of forensic work that nobody budgeted for.&lt;/p&gt;
&lt;p&gt;The mitigation is well-established. Re-jam at every break. The industry standard is at least twice per day: once at lunch, once at any other extended pause. Better still, use temperature-compensated crystal oscillators (TCXOs), which are standard in dedicated timecode generators like the Tentacle Sync E or Ambient ACN-CL. These devices drift less than 1 frame per 24 hours. A few hundred dollars per unit eliminates the crystal drift problem entirely.&lt;/p&gt;
&lt;h2&gt;4. Wrong start-of-day timecode and midnight rollover&lt;/h2&gt;
&lt;p&gt;A broadcast production sets the master timecode generator to 01:00:00:00, which is a common convention. The hour of headroom below the programme start provides space for pre-roll, bars, and tone. But someone enters 10:00:00:00 instead of 01:00:00:00. Or the generator was not reset from a previous show and still reads 23:58:00:00.&lt;/p&gt;
&lt;p&gt;If the master timecode is wrong, every device jam-synced to it inherits the error. All footage is recorded with incorrect time-of-day references. When this footage arrives at the post facility, the timecode values do not match the production logs, the script supervisor&apos;s notes, or the sound reports. Any workflow that relies on timecode to correlate picture and sound, or to locate specific takes, is broken at the source.&lt;/p&gt;
&lt;p&gt;The midnight rollover variant is more subtle and more destructive. A multi-day shoot uses time-of-day timecode without resetting at midnight. Footage shot at 11:55 PM on day one carries timecode approaching 23:59:59:29. Footage shot five minutes later at 12:05 AM on day two starts at 00:05:00:00. The timecode has wrapped past midnight.&lt;/p&gt;
&lt;p&gt;EDLs and conform tools assume timecode is monotonically increasing within a programme. When timecode wraps from 23:59:59:29 to 00:00:00:00, some tools interpret this as a backward jump. Depending on the software, the result is rejected edits, incorrect relinking, or outright crashes. This specific failure was documented often enough in the broadcast engineering literature to become a named pitfall.&lt;/p&gt;
&lt;p&gt;The conform failure is where the cost lands. The online editor receives an EDL or AAF referencing timecode values that do not match the source media. Every clip fails to relink. The conform must be done manually, which on a complex programme can take days. The audio post house receives split tracks with timecode that does not match picture. Every take must be synced by waveform or clapper.&lt;/p&gt;
&lt;p&gt;If the wrong start timecode happens to overlap with footage from a different reel or day, timecode values become ambiguous. A timecode of 10:23:15:07 could refer to two completely different frames from two different recordings, and no automated tool can resolve the ambiguity.&lt;/p&gt;
&lt;p&gt;Prevention is simple in principle: verify the master timecode generator&apos;s output before jamming any devices. Use a standardised start-of-day convention and document it. For multi-day shoots, use date-stamped user bits or embed date information in iXML metadata to disambiguate days.&lt;/p&gt;
&lt;h2&gt;5. LTC bleed into production audio&lt;/h2&gt;
&lt;p&gt;A production uses LTC (Linear Timecode) fed via audio cable to cameras and recorders. The LTC signal is a modulated audio tone oscillating between 1,200 and 2,400 Hz, and it runs through audio infrastructure alongside production sound.&lt;/p&gt;
&lt;p&gt;The bleed can happen in several ways. LTC recorded on a track of the production sound recorder crosstalks into adjacent audio tracks. A timecode generator connected via a splitter cable feeds both a timecode input and an audio input on a camera, with insufficient isolation between channels. An LTC cable runs in the same bundle as microphone cables, inductively coupling the timecode signal into the mic lines. Or a camera with a combined mic/timecode input on a 3.5mm jack gets crosstalk between the timecode and audio channels inside the camera body itself.&lt;/p&gt;
&lt;p&gt;The sound of LTC bleed is distinctive and immediately recognizable: a rapid, high-pitched ticking or buzzing, like a data modem compressed into the middle of the frequency spectrum. It is present continuously for the entire duration of recording. It does not come and go. It does not change character. It is relentless.&lt;/p&gt;
&lt;p&gt;Here is why it is nearly impossible to fix. The LTC frequency range, 1,200 to 2,400 Hz, sits directly on top of the fundamental frequencies and harmonics of human speech. This is not a coincidence of physics; it is simply that both systems operate in the band where audio equipment performs best. Any filter aggressive enough to remove the timecode signal will also remove or severely damage the dialogue it is overlapping with.&lt;/p&gt;
&lt;p&gt;LTC is not a steady-state tone. It is a frequency-modulated signal whose frequency shifts constantly as the encoded data changes. A standard notch filter, which removes a narrow frequency band, cannot track it. You would need an adaptive filter that follows the signal&apos;s modulation, and even then the overlap with speech harmonics makes clean separation extremely difficult.&lt;/p&gt;
&lt;p&gt;Modern machine-learning noise reduction tools like iZotope RX and Cedar can sometimes reduce the bleed, but rarely eliminate it completely without introducing artifacts. The processing trades one kind of damage for another: less timecode buzz, more metallic or hollow-sounding dialogue.&lt;/p&gt;
&lt;p&gt;The prevention is entirely about signal routing. Record LTC on a guard track, with at least one empty track between the timecode track and the nearest production audio track. Keep LTC cable levels at approximately -10 dBu (-20 dBFS), high enough to decode reliably but low enough to minimize crosstalk energy. Never run timecode cables in the same bundle as microphone cables. Use dedicated timecode inputs rather than splitter cables. And where possible, avoid recording LTC on production audio tracks entirely. Modern wireless timecode devices can write timecode directly to camera metadata without passing through the audio path.&lt;/p&gt;
&lt;h2&gt;The common thread&lt;/h2&gt;
&lt;p&gt;Every one of these failures shares a root cause. Timecode is a system of assumptions. It assumes all devices agree on the frame rate. It assumes they agree on the counting mode. It assumes all clocks are synchronized. It assumes the starting value is correct. It assumes the signal path is clean.&lt;/p&gt;
&lt;p&gt;When any of these assumptions is violated, and in the field, under pressure, with rented equipment and mixed crews, they are violated constantly, the result is not graceful degradation. It is binary failure. The timecode either matches and sync works, or it does not and you are rebuilding from scratch.&lt;/p&gt;
&lt;p&gt;The unifying lesson is that timecode problems are verification problems. Every disaster on this list could have been prevented by a check that takes less than thirty seconds: confirm DF/NDF mode, confirm free-run, re-jam at lunch, verify the start value, listen to the audio tracks. The thirty seconds you skip on set become thirty hours in post.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Alister Chapman, &lt;a href=&quot;https://www.xdcam-user.com/2016/12/14/notes-on-timecode-and-timecode-sync-for-cinematographers/&quot;&gt;Notes on Timecode and Timecode Sync for Cinematographers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Frame.io, &lt;a href=&quot;https://blog.frame.io/2017/07/17/timecode-and-frame-rates/&quot;&gt;Timecode and Frame Rates: Everything You Need to Know&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Frame.io, &lt;a href=&quot;https://workflow.frame.io/guide/timecode&quot;&gt;Video Post-Production Workflow Guide: Timecode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tentacle Sync, &lt;a href=&quot;https://tentaclesync.com/sync-e&quot;&gt;Sync E specifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;B&amp;amp;H Photo, &lt;a href=&quot;https://www.bhphotovideo.com/explora/video/tips-and-solutions/timecode-versus-sync-how-they-differ-and-why-it-matters&quot;&gt;Timecode versus Sync: How They Differ and Why it Matters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Production Expert, &lt;a href=&quot;https://www.production-expert.com/production-expert-1/2020/6/18/timecode-part-4-practical-applications&quot;&gt;Timecode Part 4: Practical Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Production Expert, &lt;a href=&quot;https://www.production-expert.com/production-expert-1/why-does-timecode-not-start-at-zero&quot;&gt;Why Does Timecode Not Start At Zero?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;JW Sound Group, &lt;a href=&quot;https://jwsoundgroup.net/index.php?%2Ftopic%2F37703-audio-bleeding-timecode-systems-ultrasync-one-splitter-cable%2F=&quot;&gt;Audio Bleeding: Timecode Systems Ultrasync One Splitter Cable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tentacle Sync Forum, &lt;a href=&quot;https://forum.tentaclesync.com/home/question/audio-bleed-on-camera-media-need-help-now/&quot;&gt;Audio Bleed on Camera Media&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Creative COW, &lt;a href=&quot;https://creativecow.net/forums/thread/using-timecode-ltc-smpte-on-a-film-set/&quot;&gt;Using timecode, LTC, SMPTE on a film set&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Container surgery without a DAW</title><link>https://datum.film/blog/container-surgery/</link><guid isPermaLink="true">https://datum.film/blog/container-surgery/</guid><description>Trimming a BWF file shouldn&apos;t require Pro Tools. If you understand the chunk structure, you can do it with byte offsets.</description><pubDate>Mon, 14 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A sound editor asks you to trim two seconds of silence off the head of a field recording. The file is a 48kHz/24-bit polyphonic BWF, eight channels, 47 minutes long. About 1.3 gigabytes.&lt;/p&gt;
&lt;p&gt;The normal approach: open the file in Pro Tools or a similar DAW, select the region, bounce it out. The DAW decodes the entire file into its internal mixing engine, applies the edit, then re-encodes and writes a new file. For a trim operation that removes silence, this involves billions of unnecessary sample conversions. It also takes time, consumes memory, and introduces an encode cycle that, depending on the DAW&apos;s export settings, might not even be bit-identical to the original.&lt;/p&gt;
&lt;p&gt;There is another way. If you understand the structure of the container, you can do the trim with byte arithmetic. No decoding. No encoding. No quality loss. Just moving a pointer.&lt;/p&gt;
&lt;h2&gt;The anatomy of a WAV file&lt;/h2&gt;
&lt;p&gt;A WAV file is a RIFF container. RIFF (Resource Interchange File Format) is one of the simplest container formats ever designed. The entire structure is built from chunks, each following the same pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[4-byte ID] [4-byte size (little-endian)] [payload bytes...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every chunk declares what it is and how big it is. A parser can walk through the file by reading 8 bytes, noting the chunk ID and size, then either parsing the payload or skipping ahead by &lt;code&gt;size&lt;/code&gt; bytes to reach the next chunk. That&apos;s the whole protocol.&lt;/p&gt;
&lt;p&gt;A standard WAV file contains at minimum two chunks inside the RIFF envelope:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;fmt &lt;/code&gt;&lt;/strong&gt; (note the trailing space, it&apos;s a 4-byte ID) describes the audio format. Sample rate, bit depth, channel count, block alignment. This chunk is small, typically 16 or 18 bytes of payload. Everything a decoder needs to know about how to interpret the raw samples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;data&lt;/code&gt;&lt;/strong&gt; contains the actual interleaved PCM samples. In a 47-minute 8-channel 48kHz/24-bit file, this chunk is nearly the entire file. The samples sit there in order, channel by channel within each sample frame, frame after frame. No compression, no indexing, no headers within the data. Just numbers.&lt;/p&gt;
&lt;p&gt;A Broadcast Wave Format (BWF) file adds more chunks to this base:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;bext&lt;/code&gt;&lt;/strong&gt; carries the broadcast extension metadata defined in EBU Tech 3285. The critical field is &lt;code&gt;time_reference&lt;/code&gt;, a 64-bit sample count from midnight that gives the file its position on the timeline. Also holds originator information, origination date and time, UMID, and optionally loudness measurements per EBU R 128.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;iXML&lt;/code&gt;&lt;/strong&gt; holds production metadata as XML. Scene, take, tape name, track names, channel assignments, circled-take flags, recorder model, project name. This is the chunk that lets a conform tool reconnect a recording to the script supervisor&apos;s notes.&lt;/p&gt;
&lt;p&gt;You might also find &lt;code&gt;axml&lt;/code&gt;, &lt;code&gt;LIST&lt;/code&gt;, &lt;code&gt;JUNK&lt;/code&gt; (padding for future in-place edits), or &lt;code&gt;ds64&lt;/code&gt; (the 64-bit size extension for files over 4 GB). But the principle never changes. Chunks, one after another, each self-describing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Chunk-by-chunk walkthrough of the container structure discussed here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Trimming without touching the audio&lt;/h2&gt;
&lt;p&gt;Back to the trim operation. We need to remove two seconds of silence from the head of an 8-channel, 48kHz, 24-bit file.&lt;/p&gt;
&lt;p&gt;First, the math. Two seconds at 48,000 samples per second is 96,000 sample frames. Each frame contains 8 channels at 3 bytes per sample (24-bit), so one frame is 24 bytes. The number of bytes to remove: 96,000 * 24 = 2,304,000.&lt;/p&gt;
&lt;p&gt;The trim operation is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parse the chunk table. Walk through the file, note every chunk ID, its offset, and its size. Don&apos;t read the data payloads, just the 8-byte headers.&lt;/li&gt;
&lt;li&gt;Write a new RIFF header. The total RIFF size decreases by 2,304,000 bytes.&lt;/li&gt;
&lt;li&gt;Copy the &lt;code&gt;fmt&lt;/code&gt; chunk verbatim. Nothing about the format has changed.&lt;/li&gt;
&lt;li&gt;Write the &lt;code&gt;data&lt;/code&gt; chunk header with its new size (original data size minus 2,304,000), then &lt;code&gt;seek&lt;/code&gt; past the first 2,304,000 bytes of the original data chunk and copy the rest.&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;bext&lt;/code&gt; chunk. The &lt;code&gt;time_reference&lt;/code&gt; field needs to advance by 96,000 samples, because the file now starts two seconds later on the timeline.&lt;/li&gt;
&lt;li&gt;Copy all other chunks (&lt;code&gt;iXML&lt;/code&gt;, &lt;code&gt;axml&lt;/code&gt;, &lt;code&gt;JUNK&lt;/code&gt;, anything unknown) verbatim.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result is a bit-identical subset of the original audio with corrected metadata. No sample was decoded. No sample was re-encoded. The PCM values in the output file are the exact same bytes that existed in the input file, just starting from a different offset.&lt;/p&gt;
&lt;p&gt;This is what makes it lossless in the strictest sense. Not &quot;perceptually lossless&quot; or &quot;lossless compression.&quot; Literally the same bytes.&lt;/p&gt;
&lt;h2&gt;Channel extraction&lt;/h2&gt;
&lt;p&gt;The same chunk-level thinking applies to extracting individual channels from a polyphonic file. A dialogue editor might need just the boom track (channel 1) from an 8-channel poly.&lt;/p&gt;
&lt;p&gt;PCM interleaving means the samples are laid out as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ch1 s0] [ch2 s0] [ch3 s0] ... [ch8 s0] [ch1 s1] [ch2 s1] ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Extraction walks through the data chunk one frame at a time, copying only the bytes for the desired channel. For our 24-bit 8-channel file, each frame is 24 bytes. Channel 1 occupies bytes 0 through 2 of each frame. The operation reads 24 bytes, writes 3 bytes, advances, repeats.&lt;/p&gt;
&lt;p&gt;The new file gets a &lt;code&gt;fmt&lt;/code&gt; chunk with &lt;code&gt;num_channels&lt;/code&gt; set to 1 and &lt;code&gt;block_align&lt;/code&gt; recalculated accordingly. The &lt;code&gt;data&lt;/code&gt; chunk shrinks to one-eighth its original size. The &lt;code&gt;bext&lt;/code&gt; timecode stays the same, because the temporal position hasn&apos;t changed. The &lt;code&gt;iXML&lt;/code&gt; gets updated to reflect a mono file with the correct track name.&lt;/p&gt;
&lt;p&gt;No decode. No encode. The sample values in the output are, again, the same bytes that existed in the input, just with the other channels&apos; bytes removed from between them.&lt;/p&gt;
&lt;h2&gt;Metadata rewrite&lt;/h2&gt;
&lt;p&gt;Sometimes the audio is fine and the metadata is wrong. Track names that don&apos;t match the actual content. A scene/take field left over from the previous setup. A timecode reference that&apos;s off by a frame because the recorder&apos;s word clock drifted.&lt;/p&gt;
&lt;p&gt;In a chunk-based format, metadata rewrite is the simplest operation of all. Parse the chunk table. Modify the &lt;code&gt;bext&lt;/code&gt; or &lt;code&gt;iXML&lt;/code&gt; payload. Recalculate the chunk size (it might change if the XML is shorter or longer). Write the file back: RIFF header with the new total size, then every chunk in order, with the modified metadata chunk carrying its updated payload.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;data&lt;/code&gt; chunk passes through untouched. You can pipe it from input to output without even reading it into memory. Just copy the bytes.&lt;/p&gt;
&lt;p&gt;For small metadata corrections, you can sometimes operate in-place. If the new &lt;code&gt;iXML&lt;/code&gt; payload is the same length as the old one, or shorter (padded with nulls), you can seek to the chunk&apos;s payload offset and overwrite it directly. This is why many production recorders write a &lt;code&gt;JUNK&lt;/code&gt; chunk after &lt;code&gt;iXML&lt;/code&gt;: the padding reserves space for later metadata edits without rewriting the entire file.&lt;/p&gt;
&lt;h2&gt;Why this matters&lt;/h2&gt;
&lt;p&gt;The alternative to all of this is to treat audio files as opaque blobs that only a DAW can open. Need to trim? Launch Pro Tools. Need a mono extraction? Launch Pro Tools. Need to fix a track name? Launch Pro Tools.&lt;/p&gt;
&lt;p&gt;For a facility processing hundreds of files per day, that workflow does not scale. A DAW is an editing environment, not a file manipulation tool. Using it for container surgery is like opening Photoshop to rename a JPEG.&lt;/p&gt;
&lt;p&gt;The chunk structure of RIFF/BWF is simple enough that a purpose-built tool can perform these operations in seconds, streaming data from input to output without holding the entire file in memory. A 1.3 GB poly trim allocates a few kilobytes for the chunk table and a read buffer. The operation is I/O-bound, not CPU-bound, because there is no processing. Just byte copying with arithmetic.&lt;/p&gt;
&lt;p&gt;This is the kind of operation Spool handles: batch trim, channel extraction, metadata correction, format validation, all operating at the container level. Not because we wanted to avoid using a DAW, but because the operations themselves don&apos;t require one. They never did.&lt;/p&gt;
&lt;p&gt;Understanding the container format well enough to operate on it directly isn&apos;t a shortcut. It&apos;s the correct level of abstraction for the problem. A trim is a byte offset change. A channel extraction is a stride operation. A metadata fix is a chunk rewrite. When you see them that way, the 30-second DAW bounce for a two-second trim starts to look like the workaround, not the other way around.&lt;/p&gt;
</content:encoded></item><item><title>The audio file nobody can open</title><link>https://datum.film/blog/audio-file-nobody-opens/</link><guid isPermaLink="true">https://datum.film/blog/audio-file-nobody-opens/</guid><description>Sound sends a polyphonic BWF with 24 channels. Your DAW imports two channels of silence.</description><pubDate>Mon, 30 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The production sound mixer delivers a hard drive at wrap. On it: polyphonic Broadcast Wave files, 24 channels each, meticulously recorded across boom mics, lavalieres, plant mics, and ambience rigs. Every channel is labelled. Every take has timecode. The metadata is clean.&lt;/p&gt;
&lt;p&gt;The assistant editor drags a file into their DAW. Two channels of silence.&lt;/p&gt;
&lt;p&gt;Not because the file is broken. Not because the recording failed. Because the DAW saw 24 channels, assumed stereo, took the first two, and those happened to be empty ISOs waiting for a different mixer&apos;s configuration. The other 22 channels of perfectly good production audio are right there in the file. The tool just didn&apos;t look for them.&lt;/p&gt;
&lt;h2&gt;What &quot;polyphonic&quot; means in practice&lt;/h2&gt;
&lt;p&gt;A polyphonic BWF is a single WAV file containing multiple channels of audio. On a feature film, a Sound Devices 970 or Cantar X3 might record 24 or 32 tracks simultaneously: booms, body mics, plant mics, a stereo mix, and sometimes ambisonic captures from a spatial microphone.&lt;/p&gt;
&lt;p&gt;This is different from multi-mono, where each channel gets its own file. Both approaches exist in production. Both are valid. The industry never standardised on one, so every downstream tool has to handle both. Most tools handle multi-mono fine because a mono WAV is hard to misinterpret. The problems start with polyphonic files.&lt;/p&gt;
&lt;p&gt;In a polyphonic BWF, the &lt;code&gt;fmt&lt;/code&gt; chunk tells you how many channels exist and the sample rate. That&apos;s it. It doesn&apos;t tell you what each channel is. Is channel 1 a boom? A lav? The left side of a stereo mix? A height channel from an Ambisonics array? The &lt;code&gt;fmt&lt;/code&gt; chunk doesn&apos;t know and doesn&apos;t care. It just knows there are 24 of them.&lt;/p&gt;
&lt;h2&gt;Where the channel map lives&lt;/h2&gt;
&lt;p&gt;The actual channel identification lives in two places, both optional.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;iXML chunk&lt;/strong&gt; carries a &lt;code&gt;TRACK_LIST&lt;/code&gt; element where each track gets a name, an interleave index, and sometimes a function tag. This is the production sound metadata. When a mixer labels their channels on the recorder (&quot;Boom&quot;, &quot;Lav Alice&quot;, &quot;Lav Bob&quot;, &quot;Plant SFX&quot;), those names go into iXML. A well-configured recorder writes detailed track lists. A poorly configured one writes &quot;Track 1&quot; through &quot;Track 24&quot; and calls it a day.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;channel mask&lt;/strong&gt; in the &lt;code&gt;fmt&lt;/code&gt; chunk extension (for WAVE_FORMAT_EXTENSIBLE files) maps channels to speaker positions using Microsoft&apos;s channel assignment bitmask. Left, right, centre, LFE, and so on, up to 18 predefined positions. This was designed for surround-sound playback, not production audio. There&apos;s no bitmask value for &quot;boom mic&quot; or &quot;plant mic behind the door.&quot; Production channels don&apos;t map to speaker positions because they&apos;re not meant for speakers. They&apos;re meant for a mixing stage.&lt;/p&gt;
&lt;p&gt;This is the fundamental mismatch. The channel identification system baked into the WAV format assumes you&apos;re describing a speaker layout. Production audio doesn&apos;t have a speaker layout. It has a collection of microphones that were pointed at different things. The metadata formats that actually describe production channels (iXML, the ADM/BW64 standard for object-based audio) are bolt-on additions that many tools have never heard of.&lt;/p&gt;
&lt;h2&gt;What goes wrong&lt;/h2&gt;
&lt;p&gt;A DAW that doesn&apos;t read iXML opens a 24-channel BWF and has to decide what it&apos;s looking at. Common failure modes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stereo assumption.&lt;/strong&gt; The tool assumes anything with more than one channel is stereo, takes channels 1 and 2, and ignores the rest. This is the most common failure and the most destructive, because it silently discards 22 channels of audio without warning. If channels 1 and 2 happen to be mix tracks, you get something that sounds plausible but isn&apos;t the original recordings. If they happen to be empty ISOs, you get silence and spend twenty minutes wondering if the sound department recorded anything at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Channel mask misinterpretation.&lt;/strong&gt; The tool reads the WAVE_FORMAT_EXTENSIBLE channel mask and tries to map 24 channels to a surround layout. Channels get assigned to LFE, rear surrounds, height speakers. The boom mic is now routed to the subwoofer output. The lav is playing from the right rear. Nothing is where it should be, and if you&apos;re monitoring in stereo, half the channels vanish into outputs that don&apos;t exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Partial import.&lt;/strong&gt; The tool imports all 24 channels but puts them on a single track as an interleaved surround file. You can see the waveforms, but you can&apos;t solo channel 7 or mute channel 12. The audio is all there and completely inaccessible for editorial work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ambisonics misidentification.&lt;/strong&gt; Some files carry ambisonics flags in iXML, indicating the first four channels are a B-format spatial capture (W, X, Y, Z). A tool that sees the ambisonics flag might try to decode those channels to speaker feeds, applying rotation matrices and shelf filters to what is actually a boom microphone. The audio plays back but sounds like it&apos;s been run through a broken reverb.&lt;/p&gt;
&lt;h2&gt;The silence problem&lt;/h2&gt;
&lt;p&gt;The worst outcome is silence, because silence looks like a failure to record rather than a failure to interpret. A picture editor who gets two channels of silence from a 24-channel poly file will assume the sound department didn&apos;t deliver. They&apos;ll call production. Production will check the drive, open the file in a tool that reads all channels, confirm the audio is there, and send the same file back. The editor will import it again with the same result.&lt;/p&gt;
&lt;p&gt;This loop can repeat for days before someone identifies the actual problem: the DAW is not reading the file incorrectly in a technical sense. It&apos;s reading the channels it knows how to find. It just doesn&apos;t know there are others.&lt;/p&gt;
&lt;h2&gt;Why tools don&apos;t read the metadata&lt;/h2&gt;
&lt;p&gt;The obvious question: if iXML tells you what each channel is, why don&apos;t all tools read it?&lt;/p&gt;
&lt;p&gt;Because iXML is an optional extension with no enforcement mechanism. The spec was designed by production sound manufacturers (Aaton, Sound Devices, Zaxcom) for their own equipment. It&apos;s well documented, widely used in professional production sound, and almost completely unknown outside that world. DAW developers who have never worked in film production don&apos;t know iXML exists. Video editing tools that handle BWF for the timecode in the bext chunk have no reason to parse a second chunk of XML they&apos;ve never encountered.&lt;/p&gt;
&lt;p&gt;There&apos;s also the problem of trust. Even when a tool does read iXML, the metadata might be wrong. Channel names from a recycled template. A track count in iXML that disagrees with the &lt;code&gt;fmt&lt;/code&gt; chunk. Ambisonics flags on a file that contains straight mono ISOs because the mixer toggled the wrong setting. The metadata can lie, and a tool that trusts it without verification can produce results that are wrong in more creative ways than a tool that ignores it entirely.&lt;/p&gt;
&lt;h2&gt;What correct handling looks like&lt;/h2&gt;
&lt;p&gt;A tool that handles polyphonic BWF correctly does several things.&lt;/p&gt;
&lt;p&gt;It reads the &lt;code&gt;fmt&lt;/code&gt; chunk for channel count and sample format. It reads the &lt;code&gt;bext&lt;/code&gt; chunk for timecode. Then it reads the iXML chunk for track names, functions, and layout information. If iXML is present and the track count matches &lt;code&gt;fmt&lt;/code&gt;, it presents each channel with its label. If iXML is absent or contradictory, it presents all channels with generic labels and flags the discrepancy. It never silently drops channels. It never force-fits production audio into a surround speaker layout.&lt;/p&gt;
&lt;p&gt;The key principle is: when the metadata is ambiguous, show everything and let the operator decide. Don&apos;t guess. Don&apos;t assume stereo. Don&apos;t assume surround. Don&apos;t assume anything about what a channel contains based on its position in the interleave.&lt;/p&gt;
&lt;p&gt;This is not a hard engineering problem. Parsing iXML is straightforward XML reading. Comparing the iXML track count against the &lt;code&gt;fmt&lt;/code&gt; channel count is a single integer comparison. Presenting 24 channels on 24 tracks instead of 2 is a layout decision, not a technical limitation. The tools that get this wrong aren&apos;t facing difficult constraints. They&apos;re making assumptions they never needed to make.&lt;/p&gt;
&lt;h2&gt;The pattern&lt;/h2&gt;
&lt;p&gt;This is a recurring theme in production audio: the information exists, in a well-documented format, in the file you already have. The file isn&apos;t broken. The spec isn&apos;t ambiguous. The recorder wrote everything correctly. The problem is that the tool on the receiving end has a narrower model of the world than the file demands.&lt;/p&gt;
&lt;p&gt;Twenty-four channels of production audio is not exotic. It&apos;s Tuesday on a feature film set. The formats that describe it have existed for over a decade. The failure isn&apos;t in the recording or the format or the delivery. It&apos;s in the assumption, baked into too many tools, that audio comes in stereo pairs and anything else is someone else&apos;s problem.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interactive figure: Channel inspector showing how multichannel audio metadata maps tracks and channel names.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It shouldn&apos;t take a phone call to production to find out that the audio was there all along.&lt;/p&gt;
</content:encoded></item></channel></rss>