public FractionalBrownianMotionFunction(INoiseProvider provider, Random rng, double scale, int octaves, double persistence = 0.5, double lacunarity = 2.0, Func<double, double> octaveModifier = null, double? xPeriod = null, double? yPeriod = null, SampleMode mode = SampleMode.Sample2D)
            {
                _octaveModifier = octaveModifier;
                _octaves = new Tuple<double, IFunction2D>[octaves];

                bool tiling = xPeriod != null || yPeriod != null;
                if (tiling && xPeriod == null)
                {
                    xPeriod = yPeriod;
                }
                if (tiling && yPeriod == null)
                {
                    yPeriod = xPeriod;
                }

                _maxAmplitude = 0.0;
                var amplitude = 1.0;
                var frequency = scale;

                for (var i = 0; i < octaves; i++)
                {
                    IFunction2D noise;
                    switch (mode)
                    {
                        case SampleMode.Sample2D:
                            noise = provider.Create2D(rng, frequency);
                            break;
                        case SampleMode.Slice3D:
                            noise = provider.Slice3D(rng, frequency);
                            break;
                        case SampleMode.Slice4D:
                            noise = provider.Slice4D(rng, frequency);
                            break;
                        case SampleMode.Tileable2D:
                            noise = provider.Create2D(rng, frequency, xPeriod.Value, yPeriod.Value);
                            break;
                        default:
                            throw new InvalidOperationException();
                    }
                    _octaves[i] = Tuple.Create(amplitude, noise);

                    _maxAmplitude += amplitude;
                    amplitude *= persistence;
                    frequency *= lacunarity;
                }
            }
        private IFunction2D CreateNoiseFunction(INoiseProvider provider, Random rng)
        {
            if (_octaves == 1 && _power == 1.0 && !_absoluteValue)
            {
                // Simple case that doesn't need fBm, so we can just return a basic noise function
                // to remove some overhead for benchmarking.

                switch (_mode)
                {
                    case SampleMode.Sample2D:
                        return provider.Create2D(rng, _scale);
                    case SampleMode.Slice3D:
                        return provider.Slice3D(rng, _scale);
                    case SampleMode.Slice4D:
                        return provider.Slice4D(rng, _scale);
                    case SampleMode.Tileable2D:
                        return provider.Create2D(rng, _scale, _size, _size);
                    default:
                        throw new InvalidOperationException();
                }
            }

            Func<double, double> modifier = null;

            if (_absoluteValue)
            {
                if (_power == 1.0)
                {
                    modifier = input => (1.0 - Math.Abs(input)) * 2.0 - 1.0;
                }
                else
                {
                    modifier = input => Math.Pow(1.0 - Math.Abs(input), _power) * 2.0 - 1.0;
                }
            }
            else if (_power != 1.0)
            {
                modifier = input => input < 0 ? -Math.Pow(-input, _power) : Math.Pow(input, _power);
            }

            return provider.FractionalBrownianMotion(rng, _scale, _octaves, _persistence, _lacunarity, modifier, _size, _size, _mode);
        }