# NTSC-CRT: integer-only NTSC signal emulation as a filter for games and real-time apps

> NTSC-CRT encodes and decodes an NTSC composite signal in C89 with no floating point, no third party libraries, and no SIMD, so it can drop into an emulator or a renderer as an image filter. The README is explicit that it is written for readability rather than speed, and the NES mode is a separate build with adjusted timings.

**LMP88959/NTSC-CRT** — NTSC encoding/decoding in C89 using only integers and fixed point math. Supports NES decoding. Can be used as an image filter for games or real-time applications.

- Repository: https://github.com/LMP88959/NTSC-CRT
- Stars: 576 · Forks: 31
- Language: C
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/lmp88959-ntsc-crt

## The project emulates the signal, not the look of a television

NTSC-CRT performs NTSC video signal encoding and decoding emulation by EMMIR, and the README calls the result the outcome of going down a very deep rabbit hole, with a lot learned about analog signal processing, television, and the NTSC standard along the way. That framing matters, because a filter that blurs the image and adds scanlines is a different animal from one that models the composite signal and then decodes it. Here the pipeline runs both directions, and the feature list describes what falls out of that. Relatively accurate composite NTSC image output with bandlimited luma and chroma. Color artifacts, extending to being able to show specially patterned black and white images as color. And accurate dot crawl, with a linked video showing the effect in a standard interlaced NTSC video. The audience is someone who wants the artefact behaviour of the real signal, including the parts that look like defects.

## Six constraints define the code, and they are all deliberate

The README states the restrictions the code follows, borrowing them from the author's from-scratch 3D game. Everything must be done in software, with no explicit usage of hardware acceleration. No floating point types or literals, everything integer only. No third party libraries, only the C standard library and OS libraries for window, input, and similar. No languages besides C. No compiler specific features and no SIMD. And single threaded. These are not incidental. Integer-only math is what makes the output identical across platforms, since floating point rounding differs between compilers and processors, and the no-SIMD rule keeps the code portable to any C89 compiler. The cost is stated too. The program performs relatively well and can be easily used in real-time applications, but it is by no means fully optimized, mainly for readability, so there is a lot of room for performance gains.

## Compiling is one command, and CMake adds three switches

The build is deliberately small, with a direct compile of every source file in the directory:

```sh
cd NTSC-CRT

cc -O3 -o ntsc *.c
```

There is also a CMake path for Linux, macOS, and Windows, and the README documents three available flags or variables. `LIVE` defaults to `off` and, set to `on`, enables rendering to a video window from an input PPM or BMP image file. `VIDEO` also defaults to `off`, and set to `on` enables rendering of a sequence of frames, with `video_convert.c` for details. `CRT_SYSTEM` defaults to `0`, where 0 is CRT_SYSTEM_NTSC for standard NTSC and 5 is CRT_SYSTEM_NTSCVHS for standard NTSC VHS, with `crt_core.h` for details:

```sh
cmake -B build -DLIVE=off -DVIDEO=on -DCRT_SYSTEM=5
cmake --build build --config Release
```

The repository layout matches the portability claim, with separate source and header pairs for the core and for each signal variant, `crt_core`, `crt_ntsc`, `crt_ntscvhs`, `crt_nes`, `crt_nesrgb`, `crt_snes`, and `crt_pv1k`, alongside `crt_template.c` and `crt_template.h` for custom signal encodings and `bmp_rw` and `ppm_rw` for image file input and output.

## The command line tool is a filter, and the library is the real product

The default command line takes a single PPM or BMP image file and outputs a processed PPM or BMP file. The usage text lists the flags, and one of them is the reason to reach for this tool at all. The `r` flag means raw image, and it is needed for images that use artifact colors, and the `a` flag saves the analog signal as an image instead of a decoded image, which is how you inspect what the encoder produced. The command line program does not let you mess with all the settings, and the README is clear about which ones are missing: black and white point, hue, brightness, saturation, and contrast. The reason is in the file layout. There are two main functions in `crt_main.c`, one for the command line program and one that uses the author's FW library to provide real-time NTSC emulation with adjustable parameters. The command line tool is a convenience wrapper; the library is where the configurability lives.

## NES mode is a separate build with non-standard timings

NES Mode is described as a separate version of NTSC-CRT with adjusted timings to match the non-standard NES specifications. Those timings and the extra NES specific features were incorporated into the NES version by Persune, who is thanked by name, and the extra features include the NES-specific NTSC frame pulses, dot skipping every odd frame, and border colors. The separate source files in the repository, `crt_nes.c` and `crt_nesrgb.c` with their headers, are what implement that variant, and the linked nesdev wiki page on NTSC video is the reference for the specifications. The reason the split matters is that the NES was never a broadcast television. Its timings do not match the NTSC standard a CRT would have received, so a filter tuned to the standard will not line up with NES hardware, and that is a different filter in a different file.

## Artifact colors are the point, and the waterfall effect is a side effect

Two details show what the project is for. The README includes an example of artifact colors being used purposely by specially designed art, and another of artifact colors creating a rainbow by specially designed art, both credited as not the author's own work. That is the payoff of modelling the composite signal: a designer can paint an image that relies on colour bleed, and the encoder will reproduce the bleed the same way a television would. The other detail is a warning about a side effect. The famous waterfall rainbow effect created as a result of dithering will show if it is compiled with `CRT_CHROMA_PATTERN` set to 0. So a build flag controls whether you get the authentic dithering artefact or not, and if you are rendering a modern image you may not want the rainbow, while if you are reproducing a specific historical machine you very much do.

## A hardware accelerated variant exists elsewhere

The README flags, in a heavily marked section, an AVX accelerated version written by L. Spiro in their own repository, with a link to the relevant source file in the BeesNES project. That is a different implementation of the same idea, and it is worth reading as the performance counterpart to this one. This repository's rules forbid SIMD and hardware acceleration, and the AVX version is where those constraints are lifted. So the two are complementary rather than competing. If you need the composite signal model and you also need it to keep up with a real-time workload, the base library tells you how the signal behaves and the accelerated implementation tells you how to make it fast. The licence here is a custom one that GitHub cannot classify, with the file at the repository root, and the last push was on 2026-07-19 after releases v2.3.1 in June 2025 and v2.3.2 in July 2025.

## Conclusion

NTSC-CRT fits a developer building an emulator, a retro renderer, or any real-time application that wants a CRT look from a software signal model rather than a post-process blur. It is the wrong tool if you need speed, because the README states plainly that it is not fully optimized and that there is a lot of room for performance gains, and it is the wrong tool if you need hardware acceleration, since the whole point is that nothing is accelerated. Before you adopt it, decide whether you want the composite signal model or the NES variant, because the NES mode is a separate build with its own timings, and read the note that the command line program does not expose the black and white point, hue, brightness, saturation, and contrast settings that the library exposes.

## FAQ

### What does NTSC-CRT do and what is it written in?

It performs NTSC video signal encoding and decoding emulation in C89, using only integers and fixed point math. The code follows a set of stated restrictions: software only with no hardware acceleration, no floating point, no third party libraries, C only, no compiler specific features or SIMD, and single threaded.

### How do I compile NTSC-CRT?

From the project directory, run `cc -O3 -o ntsc *.c`. The README also documents a CMake path for Linux, macOS, and Windows using `cmake -B build` with the `LIVE`, `VIDEO`, and `CRT_SYSTEM` options, followed by `cmake --build build --config Release`.

### What do the NTSC-CRT CMake options control?

`LIVE` defaults to `off` and enables rendering to a video window from an input PPM or BMP image file when set to `on`. `VIDEO` defaults to `off` and enables rendering of a sequence of frames. `CRT_SYSTEM` defaults to `0`, which is standard NTSC, while `5` selects standard NTSC VHS.

### How is NES mode different in NTSC-CRT?

NES Mode is a separate version with adjusted timings to match the non-standard NES specifications, and it was incorporated by Persune. Extra features include the NES-specific NTSC frame pulses, dot skipping every odd frame, and border colors, implemented in the `crt_nes` and `crt_nesrgb` source files.

### Which command line flags does the NTSC-CRT tool support?

The usage text lists `m` for monochrome, `o` to not prompt when overwriting files, `f` for odd field in progressive mode, `p` for progressive scan, `r` for a raw image needed when using artifact colors, `a` to save the analog signal as an image instead of the decoded image, and `h` to print help. By default the image is full color, interlaced, and scaled to the output dimensions.

## Sources

- [Issues](https://github.com/LMP88959/NTSC-CRT/issues)
- [LMP88959/NTSC-CRT on GitHub](https://github.com/LMP88959/NTSC-CRT)
- [README](https://github.com/LMP88959/NTSC-CRT/blob/main/README.md)
- [Releases](https://github.com/LMP88959/NTSC-CRT/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lmp88959-ntsc-crt
