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.
- 84-95% of C++ FastNoiseLite performance through aggressive optimization and
LLVM compilation.
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 implementations are the same.
- 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.
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
-- Extract X and Y warp offsets by slicing at z=0
warpX <- Noise.sliceX3 0.0 warpNoise
warpY <- Noise.sliceY3 0.0 warpNoise
-- 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 achieves 84-95%
of C++ FastNoiseLite performance.
- This library benefits considerably from compilation with the LLVM backend
(
-fllvm). Benchmarks suggest a ~50-80% difference depending on the kind of noise.
Parallel noise generation
This library integrates well with massiv
for parallel computation. Parallel performance can reach 10-15x single-threaded
performance.
This is the recommended approach for generating large noise textures or datasets.
Benchmarks
Results
Measured by values / second generated by the noise functions. These results come
from a benchmark with -fllvm enabled.
There's inevitably some noise in the measurements because all of the results are
forced into an unboxed vector.
2D
| name |
Float (values/sec) |
Double (values/sec) |
| value2 |
173_511_654 |
189_119_731 |
| perlin2 |
154_674_464 |
161_114_532 |
| openSimplex2 |
74_747_031 |
74_332_345 |
| valueCubic2 |
61_415_544 |
62_481_313 |
| superSimplex2 |
51_295_369 |
50_383_577 |
| cellular2 |
34_996_382 |
32_652_899 |
3D
| name |
Float (values/sec) |
Double (values/sec) |
| value3 |
90_805_572 |
93_188_363 |
| perlin3 |
74_080_032 |
82_477_882 |
| valueCubic3 |
18_765_912 |
18_284_749 |
Examples
There's an interactive demo app in the demo directory.
OpenSimplex2

Perlin

Cellular
