pure-noise
Performant, modern noise generation for Haskell with a minimal dependency footprint.
Core features
- algebraic composition of noise functions. You can combine,
layer, and transform noise sources using standard operators (E.g.,
Num,
Fractional, Monad, etc).
- Complex effects like domain warping and multi-octave fractals with clean,
type-safe composition.
- Competitive with C++: roughly 70-100% of FastNoiseLite's single-threaded
random-access throughput under the LLVM backend. And 2D cellular noise runs
ahead of C++, measured against an optimized FNL build.
For detailed FastNoiseLite comparison, methodology, and reproducibility instructions,
see the benchmark README.
The public interface for this library is unlikely to change much, although the
implementations (noiseBaseN functions and anything in Numeric.Noise.Internal)
are subject to change and may change between minor versions.
Acknowledgments
- This project grew from a port of the excellent
FastNoiseLite library. The library
structure has been tuned to perform well in Haskell and fit well with Haskell
semantics, but the core noise share their origin with FNL.
- All credit for the original design, algorithms, and implementation goes to its
creator Jordan Peck (@Auburn). I'm grateful for
their work and the opportunity to learn from it.
- The original FastNoiseLite code, from which the core algorithms in this library
were originally ported, is (C) 2020 Jordan Peck and is licensed under the MIT
license, a copy of which is included in this repository.
FastNoiseLite compatibility
pure-noise shares its lineage with FNL, but it isn't intended as a 1:1 port —
kernels are restructured for GHC, and some families intentionally diverge.
Where outputs stand today:
| family |
output vs FNL |
perlin2/3, cellular2/3 |
bit-exact |
openSimplex2/3, superSimplex2/3 |
within a few ULP |
value2/3, valueCubic2/3 |
diverges (hash finalization) |
fractal2/3, ridged2/3, pingPong2/3 |
diverges (octave normalization and weighting) |
billow2/3 |
no FNL counterpart |
[!IMPORTANT]
The value, valueCubic, and fractal families will align with FNL in 0.3,
which changes their output for a given seed. Pin pure-noise < 0.3 if you
depend on seed-stable output from them.
Usage
The library provides composable noise functions. Noise2 and Noise3 are type
aliases for 2D and 3D noise. Noise functions can be composed transparently using
standard operators with minimal performance cost.
Noise values are generally clamped to [-1, 1], although some noise functions
may occasionally produce values slightly outside this range.
Basic Example
import Numeric.Noise qualified as Noise
-- Compose multiple noise sources
myNoise2 :: (RealFrac a) => Noise.Seed -> a -> a -> a
myNoise2 =
let fractalConfig = Noise.defaultFractalConfig
combined = (Noise.perlin2 + Noise.superSimplex2) / 2
in Noise.noise2At $ Noise.fractal2 fractalConfig combined
Advanced Features
The library's unified Noise p v type enables powerful composition patterns:
Complex Compositions
The Monad instance is useful to create noise that depends on other noise values:
-- Use one noise function's output to modulate another
complexNoise :: Noise.Noise2 Float
complexNoise = do
baseNoise <- Noise.perlin2
detailNoise <- Noise.next2 Noise.superSimplex2
-- Blend based on base noise: smooth areas get less detail
pure $ baseNoise * 0.7 + detailNoise * (0.3 * (1 + baseNoise) / 2)
This is especially useful for creating organic, varied terrain where one noise pattern
influences the characteristics of another.
1D Noise via Slicing
Generate 1D noise by slicing higher-dimensional noise at a fixed coordinate:
-- Create 1D noise by fixing one dimension
noise1d :: Noise.Noise1 Float
noise1d = Noise.sliceY2 0.0 Noise.perlin2
-- Evaluate at a point
value = Noise.noise1At noise1d seed 5.0
Coordinate Transformation:
Scale, rotate, or warp the coordinate space:
-- Double the frequency
scaled = Noise.warp (\(x, y) -> (x * 2, y * 2)) Noise.perlin2
-- Rotate 45 degrees
rotated = Noise.warp (\(x, y) ->
let a = pi / 4
in (x * cos a - y * sin a, x * sin a + y * cos a)) Noise.perlin2
Layering Independent Noise
Use reseed or next2/next3 to create independent layers:
layered = (Noise.perlin2 + Noise.next2 Noise.perlin2) / 2
More examples can be found in bench and demo.
Domain Warping
Domain warping uses one noise function to distort the coordinate space of another,
creating organic, flowing patterns ideal for terrain, clouds, and natural textures:
domainWarped :: Noise.Noise2 Float
domainWarped = do
-- Generate 3D fractal for warp offsets
let warpNoise = Noise.fractal3 Noise.defaultFractalConfig{Noise.octaves = 5} Noise.perlin3
-- Sample 3D noise at different slices to create warp offsets
warpX <- Noise.sliceX3 0.0 warpNoise -- Samples at (0, x, y)
warpY <- Noise.sliceY3 0.0 warpNoise -- Samples at (x, 0, y)
-- Apply warping to base noise coordinates
Noise.warp (\(x, y) -> (x + 30 * warpX, y + 30 * warpY))
$ Noise.fractal2 Noise.defaultFractalConfig{Noise.octaves = 5} Noise.openSimplex2

See the demo app for an interactive version with adjustable parameters.
- In single-threaded scenarios with LLVM enabled, this library reaches
roughly 70-100% of C++ FastNoiseLite throughput at random access, with 2D
cellular noise ~7% faster than C++. Grid-coherent workloads measure lower
for the simplex family. These numbers are measured against an optimized
FNL build (AVX2+FMA, FP contraction on).
- This library performs significantly better under the LLVM backend
(
-fllvm); the native code generator is not recommended where generation
speed is a real concern.
- See the benchmark README
for per-algorithm, per-workload results and methodology.
Recommended build flags
Native optimization flags for the library
For the library itself, copy cabal.project.local.template into your project
(+optimize is on by default; +mfma +mavx are safe on modern x86-64).
For bulk generation, prefer parallel evaluation via massiv.
Fused multiply add
For the fastest downstream executables, compile the modules that call the
noise functions (the kernels inline into your code) with:
-fllvm -mavx -mfma -optlc-fp-contract=fast
[!WARNING]
- Results differ from a build without fma fusion by a few ULP.
- Fusion decisions may vary across LLVM versions.
Skip this flag if you need bit-identical output across builds/toolchains.
- You must use
-fllvm to use this flag. It has no effect on the native
code generator.
-optlc-fp-contract=fast lets LLVM fuse multiply-add chains into FMA
instructions, an optimization modern C++ compilers apply by default. This
gives a ~5-15% improvement on these kernels in testing.
Parallel noise generation
This library integrates well with massiv
for parallel computation. Parallel evaluation reaches roughly 6-9x
single-threaded throughput on a 14-core machine in pinned-clock measurements.
[!IMPORTANT]
Massiv integration is the recommended approach for generating large noise
textures or datasets.
Benchmarks
Results
Measured by values / second generated by the noise functions, in the
llvm-bench configuration (LLVM backend + FP contraction), on an i7-1370P
pinned at 1.9 GHz for measurement stability —
Absolute figures scale with CPU clock. The FastNoiseLite ratios are intended
to be clock-invariant.
There's inevitably some noise in the measurements because the results are forced
into an unboxed vector.
[!NOTE]
These numbers are lower than the initial release because they were re-run on a
slower processor. Order-of-magnitude/comparative difference remains reasonably
stable. See the benchmark README for details.
2D
| name |
Float (values/sec) |
Double (values/sec) |
| value2 |
64_407_126 |
68_260_653 |
| perlin2 |
61_301_663 |
65_143_707 |
| openSimplex2 |
25_982_291 |
27_045_472 |
| valueCubic2 |
22_743_642 |
23_403_842 |
| superSimplex2 |
17_167_069 |
17_762_669 |
| cellular2 |
16_025_950 |
16_007_044 |
3D
| name |
Float (values/sec) |
Double (values/sec) |
| value3 |
34_673_623 |
35_929_146 |
| perlin3 |
29_325_590 |
30_432_482 |
| openSimplex3 |
10_975_857 |
10_922_644 |
| superSimplex3 |
9_232_128 |
9_166_843 |
| valueCubic3 |
7_453_365 |
7_278_612 |
| cellular3 |
5_238_497 |
5_061_911 |
Examples
There's an interactive demo app in the demo directory.
OpenSimplex2

Perlin

Cellular
