pure-noise: Performant, modern noise generation (Perlin, OpenSimplex2, Cellular)

[ bsd3, library, math, noise, numeric ] [ Propose Tags ] [ Report a vulnerability ]

Fast, modern noise generation In pure Haskell with an algebraic interface. Provides Perlin, OpenSimplex2, OpenSimplex2S, Value, and Cellular noise variants.


[Skip to Readme]

Flags

Manual Flags

NameDescriptionDefault
llvm-bench

Build the benchmark suites via GHC's LLVM backend with a pinned toolchain (-fllvm, -pgmlo opt, -pgmlc llc, -pgmlas clang) plus -mavx -mfma and fast FP contraction in llc (-optlc-fp-contract=fast). Requires optllcclang on PATH (LLVM 19 for GHC 9.12) and GHC= 9.10 (for -pgmlas). Off by default so `cabal build --enable-benchmarks` works on machines and CI runners without LLVM. Published benchmark numbers are collected with this flag on; see bench/README.md.

Disabled
mavx

Compile with AVX instructions

Disabled
mfma

Compile with native fused-multiply-add instructions

Disabled
optimize

Turns on -O2 for pure-noise. Since the library is pretty small, this shouldn't be too much trouble, but you can disable this flag if it's slowing your builds too much.

Rationale: - -O1 leaves ~3x on the table for tight noise loops in downstream code. - -O2 seems to produce higher-quality unfoldings, which in turn causes pure-noise's kernels to inline and optimize more reliably at call sites.

Enabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.0.1, 0.2.0.0, 0.2.1.0, 0.2.1.1, 0.2.2.0
Change log CHANGELOG.md
Dependencies base (>=4.16 && <5), primitive (>=0.8 && <0.10) [details]
Tested with ghc ==9.6.7, ghc ==9.8.4, ghc ==9.12.2
License BSD-3-Clause
Copyright 2026 Jeremy Nuttall
Author Jeremy Nuttall
Maintainer jeremy@jeremy-nuttall.com
Uploaded by jtnuttall at 2026-07-19T17:43:17Z
Category Math, Numeric, Noise
Home page https://github.com/jtnuttall/pure-noise#readme
Bug tracker https://github.com/jtnuttall/pure-noise/issues
Source repo head: git clone https://github.com/jtnuttall/pure-noise
Distributions
Downloads 201 total (18 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for pure-noise-0.2.2.0

[back to package description]

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

Domain Warped Noise

See the demo app for an interactive version with adjustable parameters.

Performance notes

  • 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.

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]

  1. Results differ from a build without fma fusion by a few ULP.
  2. Fusion decisions may vary across LLVM versions. Skip this flag if you need bit-identical output across builds/toolchains.
  3. 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

OpenSimplex2 OpenSimplex2 ridged

Perlin

Perlin fBm

Cellular

value distance2add