[
  {
    "path": "ChangeLog.md",
    "content": "#Smooth.js Change Log\n\nThis is a log of changes to the library itself. Other changes, like tests, are omitted.\n\n##0.1.7\n\n* Added properties to smoothed functions: `config`, `domain`, `count`, and `dimension`.\n\n* Made code more concise.\n\n##0.1.6\n\n* Fixed bug where `Smooth()` would modify the config object passed to it, rather than working on a copy\n\n##0.1.5\n\n* Lanczos interpolation. See [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling).\n\n* Windowed sinc filter interpolation. See [sinc filter](http://en.wikipedia.org/wiki/Sinc_filter). \n\n* Deep input validation; input arrays are now thoroughly examined when calling `Smooth()`. Disable with \n`Smooth.deepValidation = false`. \n\n* `scaleTo` with ranges; you can now scale the function's domain to fit to a specific range.\n\n\n##0.1.3\n\n* New `scaleTo` config option scales the output function's domain.\n\n* Changed enums to string constants. For example, you can use either `Smooth.METHOD_CUBIC` or just `'cubic'`.\n\n##0.1.2\n\n* Fixed tension parameter bug in cubic splines. Catmull-Rom splines are now the default.\n\n\n##0.1.0\n\n(Initial release)"
  },
  {
    "path": "Readme.md",
    "content": "![Smooth.js](images/logo-white.png)\n\n#### Table of Contents\n[        What is this for?](#rm-what)<br/>\n[        How do I use it?](#rm-how)<br/>\n[                Configuration](#rm-config)<br/>\n[                        Interpolation Methods](#rm-method)<br/>\n[                        Clipping Modes](#rm-clip)<br/>\n[                        Scaling](#rm-scale)<br/>\n[                        Validation](#rm-valid)<br/>\n[                Interpolating Vectors](#rm-vec)<br/>\n[                Function Properties](#rm-prop)<br/>\n[        Future Plans](#rm-future)<br/>\n\n<a name = \"rm-what\" />\n\n# What is this for?\n\nSmooth.js takes an array of numbers or vectors and returns a parametric function that continuously interpolates\nthat array. Smooth.js supports several interpolation methods, and flexible options for boundary behavior.\n\nSmooth.js is written in clean, easy-to-read CoffeeScript, and has no external dependencies. It is licensed \nunder the permissive MIT license, so you can use it in just about any project.\n\nThis [demo](http://osuushi.github.com/plotdemo016.html) (requires a modern browser) gives a \nvisualization of the interpolation Smooth.js performs.\n\n<a name = \"rm-how\" />\n\n# How do I use it?\n\nYou can compile to javascript from the Smooth.coffee source file, or \n[download the latest compiled release](https://github.com/downloads/osuushi/Smooth.js/Smooth-0.1.7.js)\n\nSmooth.js exposes one public function, `Smooth`. The simplest use case is like this:\n\n```js\nvar s = Smooth([1, 2, 3, 4]);\nconsole.log(s(1));\t\t\t// => 2\nconsole.log(s(1.5));\t\t// => 2.5\n```\n\nThe first line will make `s` a function that interpolates the array [1,2,3,4] as a cubic spline. the second line\nwill print out index 1 of the array, which is 2. The third line *interpolates* \nhalfway between indexes 1 and 2 of the array, yielding 2.5\n\n<a name = \"rm-config\" />\n\n## Configuration\n\nThe `Smooth` function can take an object as an optional second argument which specifies the configuration \noptions described below.\n\n<a name = \"rm-method\" />\n\n### Interpolation Methods\n\n        (For visual illustrations of these interpolation methods see \n[the wiki](https://github.com/osuushi/Smooth.js/wiki/Interpolation-Methods))\n\nThe `method` config option specifies the interpolation method. There are three possible values for this \noption:\n\n#### Nearest Neighbor\n\n```js\nSmooth.METHOD_NEAREST = 'nearest'\n```\n\nThis interpolation method is like stair steps. The parameter is simply rounded to the nearest integer and \nthat element of the array is returned.\n\nTime complexity to interpolate a point: O(1)\n\n#### Linear\n\n```js\nSmooth.METHOD_LINEAR = 'linear'\n```\n\nLinear interpolation creates line segments between the input points and interpolates along those segments. \nWhile smoother than nearest neighbor, this interpolation method produces sharp corners where the parameter is\nan integer.\n\nTime complexity to interpolate a point: O(1)\n\n#### Cubic\n\n```js\nSmooth.METHOD_CUBIC = 'cubic'\n```\n\nThis is the default interpolation method, which turns the array into a \n[cubic Hermite spline](http://en.wikipedia.org/wiki/Cubic_Hermite_spline). This method is very smooth and will\nnot produce sharp corners.\n\nThe cubic Hermite spline used by Smooth.js is known as a \n[cardinal spline](http://en.wikipedia.org/wiki/Cubic_hermite_spline#Cardinal_spline). This kind of spline \nallows you to choose a \"tension\" parameter as the `cubicTension` field of the config object. Two constants are\nprovided for this value: `Smooth.CUBIC_TENSION_DEFAULT` and `Smooth.CUBIC_TENSION_CATMULL_ROM`, but you can \nuse any value between 0 and 1.\n\n`Smooth.CUBIC_TENSION_CATMULL_ROM` produces a \n[Catmull-Rom spline](http://en.wikipedia.org/wiki/Cubic_hermite_spline#Catmull.E2.80.93Rom_spline), which is commonly\nused for inbetweening keyframe animations. It is equal to a tension parameter of zero.\n\n`Smooth.CUBIC_TENSION_DEFAULT` is an alias for `CUBIC_TENSION_CATMULL_ROM`.\n\nTime complexity to interpolate a point: O(1)\n\n#### Windowed sinc filter\n\n```js\nSmooth.METHOD_SINC = 'sinc'\n```\n\nInterpolate by applying a windowed version of the [sinc filter](http://en.wikipedia.org/wiki/Sinc_filter).\n\nYou can specify the size of the window with the `sincFilterSize` config parameter. The window will extend by\nthis value in either direction from the origin. This value must be a positive integer. The default is 2.\n\nYou must also provide a window function via the `sincWindow` configuration option. This function should take\none numeric parameter and return a numeric value. For example:\n\n```js\nvar s = Smooth([1, 2, 3], {\n\tmethod: 'sinc',\n\tsincFilterSize: 2\n\tsincWindow: function(x) { return Math.exp(-x * x); }\n});\n```\n\nwill create a sinc filter with a Gaussian window function.\n\nThe window function is implicitly further multiplied by a rectangular window determined by sincFilterSize, so\n\n```js\n\tsincWindow: function(x) { return 1; }\n```\n\nwill create a sinc filter with a simple rectangular window function.\n\nTime complexity to interpolate a point: O(N), where N = `sincFilterSize` (assuming your window function is\nO(1))\n\n#### Lanczos\n\n```js\nSmooth.METHOD_LANCZOS = 'lanczos'\n```\n\nInterpolate via [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling). Convolves the input\narray by a Lanczos kernel to produce intermediate points.\n\nThe size of the Lanczos kernel can be specified via the `lanczosFilterSize` config parameter (default = 2). \nThis parameter should be a positive integer.\n\n**Note:** This filter is actually a specific case of the sinc filter. The `lanczosFilterSize` config option\nis an alias for `sincFilterSize`, and the Lanczos window function is automatically created for you based on \nthis parameter. \n\nTime complexity to interpolate a point: O(N), where N = `lanczosFilterSize`\n\n<a name = \"rm-clip\" />\n\n### Clipping modes\n\nIn addition to interpolating an array, Smooth.js allows you to specify the behavior of the output function \nwhen the parameter is outside the array's bounds. This also has an effect on cubic and sinc interpolation when \ninterpolating near the array's bounds.\n\nThe `clip` config option specifies the clipping mode, and can take the following values:\n\n#### Clamp\n\n```js\nSmooth.CLIP_CLAMP = 'clamp'\n```\n\nThe default clipping mode; the ends of the array are simply repeated to infinity.\n\n#### Zero\n\n```js\nSmooth.CLIP_ZERO = 'zero'\n```\n\nOutside the array bounds, the value drops to zero.\n\n#### Periodic\n\n```js\nSmooth.CLIP_PERIODIC = 'periodic'\n```\n\nThe whole array repeats infinitely in both directions. This is useful, for example, if you want values for a\nlooping animation.\n\n\n#### Mirror\n\n```js\nSmooth.CLIP_MIRROR = 'mirror'\n```\n\nRepeats the array infinitely in both directions, reflecting each time. For example, if you applied this to \n`[1, 2, 3, 4]` then the result would be `[1, 2, 3, 4, 3, 2, 1, 2, 3, 4...]`. Useful for \"loop back and forth\" style \nanimations, for example.\n\n<a name = \"rm-scale\" />\n\n### Scaling\n\nThe `scaleTo` config option allows you to scale the domain of the function. The default value is 0, which \ntells Smooth.js to leave the domain like the original array, so that for any integer `i`, `s(i) == arr[i]`.\n\nSetting the `scaleTo` option to non-zero will scale the domain to that value. For example:\n\n```js\nvar s = Smooth([1, 2, 3], { scaleTo: 1 });\nconsole.log(s(0));\t\t// => 1\nconsole.log(s(1 / 2));\t\t// => 2\nconsole.log(s(1));\t\t// => 3\n```\n\nYou can also provide a range for the `scaleTo` option, as an array of two numbers. This will scale the \nfunction to fit in that range. For example\n\n```js\nvar s = Smooth([1, 2, 3], { scaleTo: [10, 12] });\nconsole.log(s(10));\t\t// => 1\nconsole.log(s(12));\t\t// => 2\nconsole.log(s(14));\t\t// => 3\n```\n\nWhen using `Smooth.CLIP_PERIODIC`, the behavior of the `scaleTo` option is slightly different; instead of\nscaling to place the end of the array at the value of `scaleTo`, the value is used as the *period* of the\nfunction.\n\nFor the sake of readability, the `period` config option is aliased to `scaleTo`. Thus:\n\n```js\nvar s = Smooth([1, 2, 3], { period: 1, clip:Smooth.CLIP_PERIODIC });\nconsole.log(s(0));\t\t// => 1\nconsole.log(s(1 / 3));\t\t// => 2\nconsole.log(s(2 / 3));\t\t// => 3\nconsole.log(s(1));\t\t// => 1\n```\n\n<a name=\"rm-valid\" />\n\n### Validation\n\nBy default the input array you pass to `Smooth` will be examined thoroughly to make sure that the input is \nvalid, and exceptions will be thrown if any problems are found. This can be a performance consideration if you\nare dealing with large amounts of data.\n\nThis deep validation behavior can be disabled globally like so:\n\n```js\nSmooth.deepValidation = false;\n```\n\nThis will cause the Smooth function to only validate the first element of each array, and only minimally.\n\n\n<a name = \"rm-vec\" />\n\n## Interpolating Vectors\n\nSo far all of the example code we've seen has used scalar arrays, but Smooth.js supports interpolation of \nvectors of arbitrary dimension. Simply supply the vectors as arrays. For example, this code:\n\n```js\nvar points = [\n\t[0, 1],\n\t[4, 5],\n\t[5, 3],\n\t[2, 0]\n];\n\nvar path = Smooth(points, {\n\tmethod: Smooth.METHOD_CUBIC, \n\tclip: Smooth.CLIP_PERIODIC, \n\tcubicTension: Smooth.CUBIC_TENSION_CATMULL_ROM\n});\n```\n\ncould be used to create a path function along which to animate a sprite in a loop.\n\n<a name = \"rm-prop\" />\n\n## Function Properties\n\nThe function returned by `Smooth()` has a few properties which provide information about it. **Changing these\nproperties has no effect on the function.**\n\n`s.config` : a shallow copy of the config object you provided when creating the function. If you did not \nprovide a config object, `s.config` will be an empty object. Note that this is a *shallow* copy, so any \nmodifications you make to object properties of the config will be reflected by `s.config`, although the \nbehavior of `s` itself will not be affected.\n\n`s.domain` : The interval on which the function is defined. Outside of this interval, the function's behavior\nis determined by the clipping mode. This property is affected by the `scaleTo` parameter.\n\n`s.count` : The number of elements in the input array.\n\n`s.dimension` : If the input array contains scalar numbers, `s.dimension` will be `'scalar'`. If the input \narray contains vectors, `s.dimension` will be the vector size.\n\n<a name = \"rm-future\" />\n\n# Future Plans\n\n* Interpolation of non-uniform arrays (objects with arbitrary numeric indexes)\n* More interpolation methods\n* Custom interpolation methods (maybe)\n"
  },
  {
    "path": "Smooth.coffee",
    "content": "###\nSmooth.js version 0.1.7\n\nTurn arrays into smooth functions.\n\nCopyright 2012 Spencer Cohen\nLicensed under MIT license (see \"Smooth.js MIT license.txt\")\n\n###\n\n\n###Constants (these are accessible by Smooth.WHATEVER in user space)###\nEnum = \n\t###Interpolation methods###\n\tMETHOD_NEAREST: 'nearest' #Rounds to nearest whole index\n\tMETHOD_LINEAR: 'linear' \n\tMETHOD_CUBIC: 'cubic' # Default: cubic interpolation\n\tMETHOD_LANCZOS: 'lanczos'\n\tMETHOD_SINC: 'sinc'\n\n\t###Input clipping modes###\n\tCLIP_CLAMP: 'clamp' # Default: clamp to [0, arr.length-1]\n\tCLIP_ZERO: 'zero' # When out of bounds, clip to zero\n\tCLIP_PERIODIC: 'periodic' # Repeat the array infinitely in either direction\n\tCLIP_MIRROR: 'mirror' # Repeat infinitely in either direction, flipping each time\n\n\t### Constants for control over the cubic interpolation tension ###\n\tCUBIC_TENSION_DEFAULT: 0 # Default tension value\n\tCUBIC_TENSION_CATMULL_ROM: 0\n\n\ndefaultConfig = \n\tmethod: Enum.METHOD_CUBIC                       #The interpolation method\n\t\n\tcubicTension: Enum.CUBIC_TENSION_DEFAULT        #The cubic tension parameter\n\t\n\tclip: Enum.CLIP_CLAMP                           #The clipping mode\n\t\n\tscaleTo: 0                                      #The scale-to value (0 means don't scale) (can also be a range)\n\t\n\tsincFilterSize: 2                               #The size of the sinc filter kernel (must be an integer)\n\n\tsincWindow: undefined                           #The window function for the sinc filter\n\n###Index clipping functions###\nclipClamp = (i, n) -> Math.max 0, Math.min i, n - 1\n\nclipPeriodic = (i, n) ->\n\ti = i % n #wrap\n\ti += n if i < 0 #if negative, wrap back around\n\ti\n\nclipMirror = (i, n) ->\n\tperiod = 2*(n - 1) #period of index mirroring function\n\ti = clipPeriodic i, period\n\ti = period - i if i > n - 1 #flip when out of bounds \n\ti\n\n\n###\nAbstract scalar interpolation class which provides common functionality for all interpolators\n\nSubclasses must override interpolate().\n###\n\nclass AbstractInterpolator\n\n\tconstructor: (array, config) ->\n\t\t@array = array.slice 0 #copy the array\n\t\t@length = @array.length #cache length\n\n\t\t#Set the clipping helper method\n\t\tthrow \"Invalid clip: #{config.clip}\" unless @clipHelper = {\n\t\t\tclamp: @clipHelperClamp\n\t\t\tzero: @clipHelperZero\n\t\t\tperiodic: @clipHelperPeriodic\n\t\t\tmirror: @clipHelperMirror\n\t\t}[config.clip]\n\n\n    # Get input array value at i, applying the clipping method\n\tgetClippedInput: (i) ->\n\t\t#Normal behavior for indexes within bounds\n\t\tif 0 <= i < @length\n\t\t\t@array[i]\n\t\telse\n\t\t\t@clipHelper i\n\n\tclipHelperClamp: (i) -> @array[clipClamp i, @length]\n\n\tclipHelperZero: (i) -> 0\n\n\tclipHelperPeriodic: (i) -> @array[clipPeriodic i, @length]\n\n\tclipHelperMirror: (i) -> @array[clipMirror i, @length]\n\n\tinterpolate: (t) -> throw 'Subclasses of AbstractInterpolator must override the interpolate() method.'\n\n\n#Nearest neighbor interpolator (round to whole index)\nclass NearestInterpolator extends AbstractInterpolator\n\tinterpolate: (t) -> @getClippedInput Math.round t\n\n\n#Linear interpolator (first order Bezier)\nclass LinearInterpolator extends AbstractInterpolator\n\tinterpolate: (t) ->\n\t\tk = Math.floor t\n\t\t#Translate t to interpolate between k and k+1\n\t\tt -= k\n\t\treturn (1-t)*@getClippedInput(k) + (t)*@getClippedInput(k+1)\n\n\nclass CubicInterpolator extends AbstractInterpolator\n\tconstructor: (array, config)->\n\t\t#clamp cubic tension to [0,1] range\n\t\t@tangentFactor = 1 - Math.max 0, Math.min 1, config.cubicTension\n\t\tsuper\n\n\t# Cardinal spline with tension 0.5)\n\tgetTangent: (k) -> @tangentFactor*(@getClippedInput(k + 1) - @getClippedInput(k - 1))/2\n\n\tinterpolate: (t) ->\n\t\tk = Math.floor t\n\t\tm = [(@getTangent k), (@getTangent k+1)] #get tangents\n\t\tp = [(@getClippedInput k), (@getClippedInput k+1)] #get points\n\t\t#Translate t to interpolate between k and k+1\n\t\tt -= k\n\t\tt2 = t*t #t^2\n\t\tt3 = t*t2 #t^3\n\t\t#Apply cubic hermite spline formula\n\t\treturn (2*t3 - 3*t2 + 1)*p[0] + (t3 - 2*t2 + t)*m[0] + (-2*t3 + 3*t2)*p[1] + (t3 - t2)*m[1]\n\n{sin, PI} = Math\n#Normalized sinc function\nsinc = (x) -> if x is 0 then 1 else sin(PI*x)/(PI*x)\n\n#Make a lanczos window function for a given filter size 'a'\nmakeLanczosWindow = (a) -> (x) -> sinc(x/a)\n\n#Make a sinc kernel function by multiplying the sinc function by a window function\nmakeSincKernel = (window) -> (x) -> sinc(x)*window(x)\n\nclass SincFilterInterpolator extends AbstractInterpolator\n\tconstructor: (array, config) ->\n\t\tsuper\n\t\t#Create the lanczos kernel function\n\t\t@a = config.sincFilterSize\n\n\t\t#Cannot make sinc filter without a window function\n\t\tthrow 'No sincWindow provided' unless config.sincWindow\n\t\t#Window the sinc function to make the kernel\n\t\t@kernel = makeSincKernel config.sincWindow\n\n\tinterpolate: (t) ->\n\t\tk = Math.floor t\n\t\t#Convolve with Lanczos kernel\n\t\tsum = 0\n\t\tsum += @kernel(t - n)*@getClippedInput(n) for n in [(k - @a + 1)..(k + @a)]\n\t\tsum\n\n\n#Extract a column from a two dimensional array\ngetColumn = (arr, i) -> (row[i] for row in arr)\n\n\n#Take a function with one parameter and apply a scale factor to its parameter\nmakeScaledFunction = (f, baseScale, scaleRange) ->\n\tif scaleRange.join is '0,1'\n\t\tf #don't wrap the function unecessarily\n\telse \n\t\tscaleFactor = baseScale/(scaleRange[1] - scaleRange[0])\n\t\ttranslation = scaleRange[0]\n\t\t(t) -> f scaleFactor*(t - translation)\n\n\ngetType = (x) -> Object::toString.call(x)[('[object '.length)...-1]\n\n#Throw exception if input is not a number\nvalidateNumber = (n) ->\n\tthrow 'NaN in Smooth() input' if isNaN n\n\tthrow 'Non-number in Smooth() input' unless getType(n) is 'Number'\n\tthrow 'Infinity in Smooth() input' unless isFinite n\n\t\t\n\n#Throw an exception if input is not a vector of numbers which is the correct length\nvalidateVector = (v, dimension) ->\n\tthrow 'Non-vector in Smooth() input' unless getType(v) is 'Array'\n\tthrow 'Inconsistent dimension in Smooth() input' unless v.length is dimension\n\tvalidateNumber n for n in v\n\treturn\n\nisValidNumber = (n) -> (getType(n) is 'Number') and isFinite(n) and not isNaN(n)\n\nnormalizeScaleTo = (s) ->\n\tinvalidErr = \"scaleTo param must be number or array of two numbers\"\n\tswitch getType s\n\t\twhen 'Number'\n\t\t\tthrow invalidErr unless isValidNumber s\n\t\t\ts = [0, s]\n\t\twhen 'Array'\n\t\t\tthrow invalidErr unless s.length is 2\n\t\t\tthrow invalidErr unless isValidNumber(s[0]) and isValidNumber(s[1])\n\t\telse throw invalidErr\n\treturn s\n\nshallowCopy = (obj) ->\n\tcopy = {}\n\tcopy[k] = v for own k,v of obj\n\tcopy\n\nSmooth = (arr, config = {}) ->\n\t#Properties to copy to the function once it is created\n\tproperties = {}\n\t#Make a copy of the config object to modify\n\tconfig = shallowCopy config\n\n\t#Make another copy of the config object to save to the function\n\tproperties.config = shallowCopy config\n\n\t#Alias 'period' to 'scaleTo'\n\tconfig.scaleTo ?= config.period\n\n\t#Alias lanczosFilterSize to sincFilterSize\n\tconfig.sincFilterSize ?= config.lanczosFilterSize\n\n\tconfig[k] ?= v for own k,v of defaultConfig #fill in defaults\n\n\t#Get the interpolator class according to the configuration\n\tthrow \"Invalid method: #{config.method}\" unless interpolatorClass = {\n\t\t\tnearest: NearestInterpolator\n\t\t\tlinear: LinearInterpolator\n\t\t\tcubic: CubicInterpolator\n\t\t\tlanczos: SincFilterInterpolator #lanczos is a specific case of sinc filter\n\t\t\tsinc: SincFilterInterpolator\n\t}[config.method]\n\n\tif config.method is 'lanczos'\n\t\t#Setup lanczos window\n\t\tconfig.sincWindow = makeLanczosWindow config.sincFilterSize\n\n\n\t#Make sure there's at least one element in the input array\n\tthrow 'Array must have at least two elements' if arr.length < 2\n\n\t#save count property\n\tproperties.count = arr.length\n\n\t#See what type of data we're dealing with\n\n\tsmoothFunc = switch getType arr[0]\n\t\t\twhen 'Number' #scalar\n\t\t\t\tproperties.dimension = 'scalar'\n\t\t\t\t#Validate all input if deep validation is on\n\t\t\t\tvalidateNumber n for n in arr if Smooth.deepValidation\n\t\t\t\t#Create the interpolator\n\t\t\t\tinterpolator = new interpolatorClass arr, config\n\t\t\t\t#make function that runs the interpolator\n\t\t\t\t(t) -> interpolator.interpolate t\n\n\t\t\twhen 'Array' # vector\n\t\t\t\tproperties.dimension = dimension = arr[0].length\n\t\t\t\tthrow 'Vectors must be non-empty' unless dimension\n\t\t\t\t#Validate all input if deep validation is on\n\t\t\t\tvalidateVector v, dimension for v in arr if Smooth.deepValidation\n\t\t\t\t#Create interpolator for each column\n\t\t\t\tinterpolators = (new interpolatorClass(getColumn(arr, i), config) for i in [0...dimension])\n\t\t\t\t#make function that runs the interpolators and puts them into an array\n\t\t\t\t(t) -> (interpolator.interpolate(t) for interpolator in interpolators)\n\n\t\t\telse throw \"Invalid element type: #{getType arr[0]}\"\n\n\t# Determine the end of the original function's domain\n\tif config.clip is 'periodic' then baseDomainEnd = arr.length #after last element for periodic\n\telse baseDomainEnd = arr.length - 1 #at last element for non-periodic\n\n\tconfig.scaleTo ||= baseDomainEnd #default scales to the end of the original domain for no effect\n\t\n\tproperties.domain = normalizeScaleTo config.scaleTo\n\tsmoothFunc = makeScaledFunction smoothFunc, baseDomainEnd, properties.domain\n\tproperties.domain.sort()\n\n\t###copy properties###\n\tsmoothFunc[k] = v for own k,v of properties\n\n\treturn smoothFunc\n\n#Copy enums to Smooth\nSmooth[k] = v for own k,v of Enum\n\nSmooth.deepValidation = true\n\n(exports ? window).Smooth = Smooth\n"
  },
  {
    "path": "Smooth.js MIT license.txt",
    "content": "Smooth.js Copyright (c) 2012 Spencer Cohen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the Software without restriction, including without\nlimitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the\nSoftware, and to permit persons to whom the Software is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\nTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\nCONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"smooth.js\",\n  \"homepage\": \"https://github.com/osuushi/Smooth.js\",\n  \"authors\": [\n    \"yournamehere <someone@somewhere.com>\"\n  ],\n  \"description\": \"Smooth.js is an array interpolation library for Javascript\",\n  \"main\": \"\",\n  \"moduleType\": [],\n  \"keywords\": [\n    \"interpolation\",\n    \"javascript\",\n    \"smoothjs\"\n  ],\n  \"license\": \"MIT\",\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"test\",\n    \"tests\"\n  ]\n}\n"
  },
  {
    "path": "sindemo.coffee",
    "content": "###\nsindemo\n\nSample and cubic interpolate the sin function, then print out max and average error.\n###\n\n{Smooth} = require './Smooth'\n\ns = (Math.sin 2*Math.PI*x for x in [0...1] by 1/8)\n\n\n#Scale the function\nsmooth_sin = ((f) ->\n\tscaleVal = 0.5*s.length/Math.PI\n\treturn (x) -> f x*scaleVal\n) Smooth s, method:Smooth.METHOD_CUBIC, clip:Smooth.CLIP_PERIODIC\n\ntotalError = 0\ncount = 0\nmaxError = 0\nfor x in [-10..10] by .001\n\terror = Math.abs Math.sin(x) - smooth_sin(x)\n\tmaxError = Math.max error, maxError\n\ttotalError += error\n\tcount++\n\nconsole.log \"Max Error:\\t #{(100*maxError).toFixed(10)}%\"\nconsole.log \"Average Error:\\t #{(100*totalError/count).toFixed(10)}%\""
  },
  {
    "path": "test/specs/clip-clamp.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\ndescribe \"Clip Clamp\", ->\n\tarr = [1,2,3,4]\n\tlen = arr.length\n\ts = Smooth arr, clip:Smooth.CLIP_CLAMP\n\n\tit 'should extend first value to negative infinity', ->\n\t\texpect(s -1).toEqual arr[0]\n\t\texpect(s -5.8).toEqual arr[0]\n\t\texpect(s -100.3).toEqual arr[0]\n\n\tit 'should extend last value to positive infinity', ->\n\t\texpect(s len+1).toEqual arr[len-1]\n\t\texpect(s len+12.2).toEqual arr[len-1]\n\t\texpect(s len+1000).toEqual arr[len-1]\n\n\tit 'should leave in-bounds values untouched', ->\n\t\texpect(s 0).toEqual arr[0]\n\t\texpect(s 1).toEqual arr[1]\n\t\texpect(s 2).toEqual arr[2]"
  },
  {
    "path": "test/specs/clip-mirror.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\ndescribe \"Clip Mirror\", ->\n\tarr = [1,2,3,4]\n\tlen = arr.length\n\ts = Smooth arr, clip:Smooth.CLIP_MIRROR\n\n\tit 'should reflect across the origin', ->\n\t\texpect(s -1).toEqual arr[1]\n\t\texpect(s -2).toEqual arr[2]\n\t\texpect(s -3).toEqual arr[3]\n\t\n\tit 'should produce a predictable pattern', ->\n\t\tmirrorArray = arr.concat (arr[i] for i in [len-2...0]) #create pattern to repeat\n\t\texpect( (s i for i in [0...20]).join() )\n\t\t\t.toBe (mirrorArray[i%mirrorArray.length] for i in [0...20]).join()\n\n\n\tit 'should leave in-bounds values untouched', ->\n\t\texpect(s 0).toEqual arr[0]\n\t\texpect(s 1).toEqual arr[1]\n\t\texpect(s 3).toEqual arr[3]"
  },
  {
    "path": "test/specs/clip-perodic.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\ndescribe \"Clip Periodic\", ->\n\tarr = [1,2,3,4]\n\tlen = arr.length\n\ts = Smooth arr, clip:Smooth.CLIP_PERIODIC\n\n\tit 'should repeat the same value for a shift by any integer multiple of the array length', ->\n\t\texpect(s 0 - 98*len).toEqual arr[0]\n\t\texpect(s 1 + 12*len).toEqual arr[1]\n\t\texpect(s 2 - 13*len).toEqual arr[2]\n\t\texpect(s 3 + 47*len).toEqual arr[3]\n\n\tit 'should leave in-bounds values untouched', ->\n\t\texpect(s 0).toEqual arr[0]\n\t\texpect(s 1).toEqual arr[1]\n\t\texpect(s 3).toEqual arr[3]"
  },
  {
    "path": "test/specs/clip-zero.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\ndescribe \"Clip Zero\", ->\n\tarr = [1,2,3,4]\n\tlen = arr.length\n\ts = Smooth arr, clip:Smooth.CLIP_ZERO\n\n\tit 'should be zero when out of bounds', ->\n\t\texpect(s -1).toEqual 0\n\t\texpect(s -100.3).toEqual 0\n\t\texpect(s len+1).toEqual 0\n\t\texpect(s len+12.2).toEqual 0\n\t\texpect(s len+1000).toEqual 0\n\n\tit 'should leave in-bounds values untouched', ->\n\t\texpect(s 0).toEqual arr[0]\n\t\texpect(s 1).toEqual arr[1]\n\t\texpect(s 2).toEqual arr[2]"
  },
  {
    "path": "test/specs/cubic.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\n{deriv} = require './util.coffee'\n\n\ndescribe 'Cubic Interpolator', ->\n\tdescribe 'Catmull-Rom', ->\n\t\tarr = [4,6,-2,3]\n\t\ts = Smooth arr, method:Smooth.METHOD_CUBIC, cubicTension: Smooth.CATMULL_ROM\n\n\t\tit 'should match integer indexes', ->\n\t\t\texpect(s 0).toEqual arr[0]\n\t\t\texpect(s 1).toEqual arr[1]\n\t\t\texpect(s 2).toEqual arr[2]\n\t\t\texpect(s 3).toEqual arr[3]\n\n\t\tit 'should have neighbor-slope tangent at integers', ->\n\t\t\texpect(deriv(s) 1).toBeCloseTo (arr[2]-arr[0])/2\n\t\t\texpect(deriv(s) 2).toBeCloseTo (arr[3]-arr[1])/2\n\n\t\tit 'should repeat when periodic', ->\n\t\tp = Smooth arr, method:'cubic', cubicTension: Smooth.CATMULL_ROM, clip:'periodic', scaleTo: 1\n\t\tfor i in [-2..2] by 1/16\n\t\t\texpect(p i).toEqual p i - Math.floor i\n\n\tdescribe 'Tension=1', ->\n\t\tarr = [8,2,-4,9]\n\t\ts = Smooth arr, method:Smooth.METHOD_CUBIC, cubicTension: 1\n\n\n\t\tit 'should match integer indexes', ->\n\t\t\texpect(s 0).toEqual arr[0]\n\t\t\texpect(s 1).toEqual arr[1]\n\t\t\texpect(s 2).toEqual arr[2]\n\t\t\texpect(s 3).toEqual arr[3]\n\n\t\tit 'should have zero derivatives at integers', ->\n\t\t\texpect(deriv(s) 0).toBeCloseTo 0\n\t\t\texpect(deriv(s) 1).toBeCloseTo 0\n\t\t\texpect(deriv(s) 2).toBeCloseTo 0\n\t\t\texpect(deriv(s) 3).toBeCloseTo 0"
  },
  {
    "path": "test/specs/exceptions.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\ndescribe 'Exceptions', ->\n\tit 'should throw for invalid methods', ->\n\t\texpect(-> Smooth [1,2], method: 'lanscoz').toThrow \"Invalid method: lanscoz\"\n\t\texpect(-> Smooth [1,2], method: 'Cubic').toThrow \"Invalid method: Cubic\"\n\n\tit 'should throw for invalid clipping mode', ->\n\t\texpect(-> Smooth [1,2], clip: 'mirorr').toThrow \"Invalid clip: mirorr\"\n\t\texpect(-> Smooth [1,2], clip: 'Linear').toThrow \"Invalid clip: Linear\"\n\n\tit 'should throw for invalid arrays', ->\n\t\texpect(-> Smooth []).toThrow 'Array must have at least two elements'\n\t\texpect(-> Smooth [0]).toThrow 'Array must have at least two elements'\n\n\tit 'should throw for bad input', ->\n\t\texpect(-> Smooth ['a','b']).toThrow 'Invalid element type: String'\n\t\texpect(-> Smooth [(->), (->)]).toThrow 'Invalid element type: Function'\n\t\texpect(-> Smooth [[], []]).toThrow 'Vectors must be non-empty'\n\n\tit 'should throw for sinc filter with no window', ->\n\t\texpect(-> Smooth [1,2], method:'sinc').toThrow 'No sincWindow provided'\n\n\tit 'should throw for invalid scaleTo', ->\n\t\texpect(-> Smooth [1,2], scaleTo:[1]).toThrow 'scaleTo param must be number or array of two numbers'\n\t\texpect(-> Smooth [1,2], scaleTo:[1,2,3]).toThrow 'scaleTo param must be number or array of two numbers'\n\t\texpect(-> Smooth [1,2], scaleTo:'a').toThrow 'scaleTo param must be number or array of two numbers'\n\t\texpect(-> Smooth [1,2], scaleTo:[1,'x']).toThrow 'scaleTo param must be number or array of two numbers'\n\t\texpect(-> Smooth [1,2], scaleTo:Infinity).toThrow 'scaleTo param must be number or array of two numbers'\n\n\tdescribe 'With deep validation on...', ->\n\t\tit 'should throw for bad input deep inside the input', ->\n\t\t\tSmooth.deepValidation = true\n\t\t\texpect(-> Smooth [1,2,'a']).toThrow 'NaN in Smooth() input'\n\t\t\texpect(-> Smooth [1,2,'3']).toThrow 'Non-number in Smooth() input'\n\t\t\texpect(-> Smooth [1,2, Infinity]).toThrow 'Infinity in Smooth() input'\n\n\t\t\texpect(-> Smooth [[1],[2],['a']]).toThrow 'NaN in Smooth() input'\n\t\t\texpect(-> Smooth [[1],[2],['3']]).toThrow 'Non-number in Smooth() input'\n\t\t\texpect(-> Smooth [[1],[2], [Infinity]]).toThrow 'Infinity in Smooth() input'\n\n\t\t\texpect(-> Smooth [[1], 1]).toThrow 'Non-vector in Smooth() input'\n\t\t\texpect(-> Smooth [[1], [1,2]]).toThrow 'Inconsistent dimension in Smooth() input'\n\n\tdescribe 'With deep validation off...', ->\n\t\tit 'should not throw for bad input deep inside the input', ->\n\t\t\tSmooth.deepValidation = false\n\t\t\texpect(Smooth [1,2,'a']).toBeTruthy()\n\t\t\texpect(Smooth [1,2,'3']).toBeTruthy()\n\t\t\texpect(Smooth [1,2, Infinity]).toBeTruthy()\n\n\t\t\texpect(Smooth [[1],[2],['a']]).toBeTruthy()\n\t\t\texpect(Smooth [[1],[2],['3']]).toBeTruthy()\n\t\t\texpect(Smooth [[1],[2], [Infinity]]).toBeTruthy()\n\n\t\t\texpect(Smooth [[1], 1]).toBeTruthy()\n\t\t\texpect(Smooth [[1], [1,2]]).toBeTruthy()\n\n"
  },
  {
    "path": "test/specs/lanczos.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\n{deriv} = require './util.coffee'\n\ndescribe 'Lanczos Interpolator', ->\n\tarr = [1,3,-2,8]\n\ts = Smooth arr, method: 'lanczos'\n\n\tit 'should be close to array at integer indexes', ->\n\t\tfor i in [0...arr.length]\n\t\t\texpect(s i).toBeCloseTo arr[i]\n\n\tit 'should be continuous everywhere', ->\n\t\tdelta = 0.00001\n\t\tfor i in [-1..arr.length] by 1/64\n\t\t\texpect(s i).toBeCloseTo s(i-delta), 0\n\n\tit 'should be differentiable everywhere', ->\n\t\tdelta = 0.00001\n\t\tds = deriv s\n\t\tfor i in [-1..arr.length] by 1/64\n\t\t\texpect(ds i).toBeCloseTo ds(i-delta), 0\n\n\tit 'should repeat when periodic', ->\n\t\tp = Smooth arr, method:'lanczos', clip: 'periodic', scaleTo: 1\n\t\tfor i in [-2..2] by 1/16\n\t\t\texpect(p i).toEqual p i - Math.floor i"
  },
  {
    "path": "test/specs/linear.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\n{deriv} = require './util.coffee'\n\n\ndescribe 'Linear Interpolator', ->\n\tarr = [1,4,3,8]\n\ts = Smooth arr, method:Smooth.METHOD_LINEAR\n\n\tit 'should match integer indexes', ->\n\t\texpect(s 0).toEqual arr[0]\n\t\texpect(s 1).toEqual arr[1]\n\t\texpect(s 2).toEqual arr[2]\n\t\texpect(s 3).toEqual arr[3]\n\n\tit 'should have arithmetic mean for midpoints', ->\n\t\texpect(s 0.5).toBeCloseTo (arr[0]+arr[1])/2\n\t\texpect(s 1.5).toBeCloseTo (arr[1]+arr[2])/2\n\t\texpect(s 2.5).toBeCloseTo (arr[2]+arr[3])/2\n\n\tit 'should have derivatives equal to point differences', ->\n\t\texpect(deriv(s) 0.2).toBeCloseTo arr[1] - arr[0]\n\t\texpect(deriv(s) 0.8).toBeCloseTo arr[1] - arr[0]\n\t\texpect(deriv(s) 1.4).toBeCloseTo arr[2] - arr[1]\n\t\texpect(deriv(s) 2.7).toBeCloseTo arr[3] - arr[2]\n\n\tit 'should repeat when periodic', ->\n\t\tp = Smooth arr, method:'linear', clip: 'periodic', scaleTo: 1\n\t\tfor i in [-2..2] by 1/16\n\t\t\texpect(p i).toEqual p i - Math.floor i"
  },
  {
    "path": "test/specs/misc.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\nutil = require './util'\n\ndescribe \"Misc...\", ->\n\tit 'should not modify the original config object', ->\n\t\tconfig = lanczosFilterSize: 2\n\t\tconfigCopy = util.shallowCopy config\n\t\ts = Smooth [1,2,3], config\n\t\t#check object equality\n\t\texpect(config).toEqual configCopy\n\n"
  },
  {
    "path": "test/specs/nearest.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\n{deriv} = require './util.coffee'\n\ndescribe 'Nearest Neighbor Interpolator', ->\n\tarr = [1,2,3,4]\n\ts = Smooth arr, method:Smooth.METHOD_NEAREST\n\n\tit 'should match integer indexes', ->\n\t\texpect(s 0).toEqual arr[0]\n\t\texpect(s 1).toEqual arr[1]\n\t\texpect(s 2).toEqual arr[2]\n\t\texpect(s 3).toEqual arr[3]\n\n\tit 'should round fractional parameter', ->\n\t\texpect(s 0.1).toEqual arr[0]\n\t\texpect(s 1.9).toEqual arr[2]\n\t\texpect(s 2.2).toEqual arr[2]\n\t\texpect(s 2.8).toEqual arr[3]\n\t\n\tit 'should have zero derivatives where fraction != .5', ->\n\t\texpect(deriv(s) 1).toBeCloseTo 0\n\t\texpect(deriv(s) 1.1).toBeCloseTo 0\n\t\texpect(deriv(s) 1.9).toBeCloseTo 0\n\t\texpect(deriv(s) 3.2).toBeCloseTo 0\n\n\tit 'should repeat when periodic', ->\n\t\tp = Smooth arr, method:'nearest', clip: 'periodic', scaleTo: 1\n\t\tfor i in [-2..2] by 1/16\n\t\t\texpect(p i).toEqual p i - Math.floor i\n"
  },
  {
    "path": "test/specs/properties.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\nutil = require './util'\n\ndescribe \"Smooth function properties...\", ->\n\tit 'should save a shallow copy of the config passed in by the user', ->\n\t\tconfig = cubicTension: 1, method: 'cubic', clip: 'zero'\n\t\ts = Smooth [1,2,3], config\n\t\texpect(s.config).toEqual config\n\n\tit 'should save the domain correctly', ->\n\t\texpect((Smooth [1,1,1]).domain).toEqual [0, 2]\n\t\texpect((Smooth [1,1,1,1], clip:'periodic').domain).toEqual [0, 4]\n\t\texpect((Smooth [1,1,1], scaleTo:2).domain).toEqual [0, 2]\n\t\texpect((Smooth [1,1,1], scaleTo:[1,5]).domain).toEqual [1, 5]\n\t\texpect((Smooth [1,1,1], clip:'periodic', scaleTo:[1,5]).domain).toEqual [1, 5]\n\t\texpect((Smooth [1,1,1], scaleTo:[5,1]).domain).toEqual [1, 5]\n\n\tit 'should save the count correctly', ->\n\t\texpect(Smooth([1,1,3]).count).toBe 3\n\t\texpect(Smooth([1,2,3], scaleTo:5).count).toBe 3\n\t\n\tit 'should save the dimension correctly', ->\n\t\texpect(Smooth([1,2,3]).dimension).toBe 'scalar'\n\t\texpect(Smooth([[1],[2],[3]]).dimension).toBe 1\n\t\texpect(Smooth([[1,2],[2,3],[3,4]]).dimension).toBe 2\n"
  },
  {
    "path": "test/specs/scale.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\ndescribe \"Scale to...\", ->\n\tarr = [1,2,3,4]\n\n\tit 'should scale to [0,1]', ->\n\t\ts = Smooth arr, scaleTo: 1\n\t\texpect(s 0).toBeCloseTo arr[0]\n\t\texpect(s 1/3).toBeCloseTo arr[1]\n\t\texpect(s 2/3).toBeCloseTo arr[2]\n\t\texpect(s 1).toBeCloseTo arr[3]\n\n\tit 'should scale to [0, length-1] with no change from unscaled', ->\n\t\ts_noscale = Smooth arr\n\t\ts_scale = Smooth arr, scaleTo: arr.length-1\n\n\t\texpect(s_scale 0).toBeCloseTo s_noscale 0\n\t\texpect(s_scale 2).toBeCloseTo s_noscale 2\n\t\texpect(s_scale 2.5).toBeCloseTo s_noscale 2.5\n\t\texpect(s_scale 3).toBeCloseTo s_noscale 3\n\n\tit 'should scale to [0, 9]', ->\n\t\ts = Smooth arr, scaleTo: 9\n\t\texpect(s 0).toBeCloseTo arr[0]\n\t\texpect(s 3).toBeCloseTo arr[1]\n\t\texpect(s 6).toBeCloseTo arr[2]\n\t\texpect(s 9).toBeCloseTo arr[3]\n\n\tit 'should reflect when scaling to -(length-1)', ->\n\t\ts = Smooth arr, scaleTo: -(arr.length - 1)\n\t\texpect(s 0).toBeCloseTo arr[0]\n\t\texpect(s -1).toBeCloseTo arr[1]\n\t\texpect(s -2).toBeCloseTo arr[2]\n\t\texpect(s -3).toBeCloseTo arr[3]\n\n\tit 'should scale to the next cycle for periodic functions', ->\n\t\ts = Smooth arr, scaleTo: 1, clip:Smooth.CLIP_PERIODIC\n\t\texpect(s 0).toBeCloseTo s 1\n\t\texpect(s 0.5).toBeCloseTo s 2.5\n\t\texpect(s 3.8).toBeCloseTo s -9.2\n\n\tdescribe 'range scaling', ->\n\t\tit 'should scale to [-1, 1]', ->\n\t\t\ts = Smooth arr, scaleTo: [-1, 1]\n\t\t\texpect(s -1).toBeCloseTo arr[0]\n\t\t\texpect(s -1/3).toBeCloseTo arr[1]\n\t\t\texpect(s 1/3).toBeCloseTo arr[2]\n\t\t\texpect(s 1).toBeCloseTo arr[3]\n\n\t\tit 'should scale periodic to [0.5, 1.5]', ->\n\t\t\ts = Smooth arr, scaleTo: [0.5, 1.5], clip:'periodic'\n\t\t\texpect(s 0).toBeCloseTo s 1\n\t\t\texpect(s 0.5).toBeCloseTo s 2.5\n\t\t\texpect(s 3.8).toBeCloseTo s -9.2\n\n\n"
  },
  {
    "path": "test/specs/sinc.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\n{deriv} = require './util.coffee'\n\ndescribe 'Sinc Filter Interpolator', ->\n\tarr = [1,3,-2,8]\n\tdescribe 'Gaussian window', ->\n\t\ts = Smooth arr, method: 'sinc', sincWindow: (x) -> Math.exp -x*x\n\n\t\tit 'should be close to array at integer indexes', ->\n\t\t\tfor i in [0...arr.length]\n\t\t\t\texpect(s i).toBeCloseTo arr[i]\n\n\t\tit 'should be continuous everywhere', ->\n\t\t\tdelta = 0.00001\n\t\t\tfor i in [-1..arr.length] by 1/64\n\t\t\t\texpect(s i).toBeCloseTo s(i-delta), 0\n\n\t\tit 'should be differentiable everywhere', ->\n\t\t\tdelta = 0.00001\n\t\t\tds = deriv s\n\t\t\tfor i in [-1..arr.length] by 1/64\n\t\t\t\texpect(ds i).toBeCloseTo ds(i-delta), 0\n\n\t\tit 'should repeat when periodic', ->\n\t\t\tp = Smooth arr, method: 'sinc', scaleTo: 1, clip: 'periodic', sincWindow: (x) -> Math.exp -x*x\n\t\t\tfor i in [-2..2] by 1/16\n\t\t\t\texpect(p i).toEqual p i - Math.floor i\n\n\tdescribe 'Circular window', ->\n\t\ts = Smooth arr, method: 'sinc', sincWindow: (x) -> Math.sqrt(1 - x*x/4)\n\n\t\tit 'should be close to array at integer indexes', ->\n\t\t\tfor i in [0...arr.length]\n\t\t\t\texpect(s i).toBeCloseTo arr[i]\n\n\t\tit 'should be continuous everywhere', ->\n\t\t\tdelta = 0.00001\n\t\t\tfor i in [-1..arr.length] by 1/64\n\t\t\t\texpect(s i).toBeCloseTo s(i-delta), 0\n\n\t\tit 'should be differentiable everywhere', ->\n\t\t\tdelta = 0.00001\n\t\t\tds = deriv s\n\t\t\tfor i in [-1..arr.length] by 1/64\n\t\t\t\texpect(ds i).toBeCloseTo ds(i-delta), 0\n\n\t\tit 'should repeat when periodic', ->\n\t\t\tp = Smooth arr, method: 'sinc', scaleTo: 1, clip: 'periodic', sincWindow: (x) -> Math.sqrt(1 - x*x/4)\n\t\t\tfor i in [-2..2] by 1/16\n\t\t\t\texpect(p i).toEqual p i - Math.floor i\n\n"
  },
  {
    "path": "test/specs/util.coffee",
    "content": "### Helper code for tests ###\n\n\n#Approximate derivative function with some delta value\nexports.deriv = (f, delta = 0.0001) -> \n\t(t) -> ((f t+delta) - (f t))/delta\n\nexports.distance = (a, b) ->\n\tsqDist = 0\n\tl = a.length\n\tsqDist += Math.pow a[i]-b[i], 2 for i in [0...l]\n\treturn Math.sqrt sqDist\n\n\nexports.shallowCopy = (obj) ->\n\tcopy = {}\n\tcopy[k] = v for own k,v of obj\n\tcopy\n"
  },
  {
    "path": "test/specs/vector.spec.coffee",
    "content": "{Smooth} = require '../../Smooth.coffee'\n\n{distance} = require './util.coffee'\n\ndescribe 'Vector', ->\n\tit 'should approximate a unit circle', ->\n\t\t{sin, cos, PI} = Math\n\t\tcircle_points = ([cos(PI*t), sin(PI*t)] for t in [0...2] by 1/6)\n\t\t#Make into a function\n\t\tcircle = Smooth circle_points, period:1, clip:Smooth.CLIP_PERIODIC\n\n\t\t#Integrate arc length\n\t\tl = 0\n\t\tstart = [1,0]\n\t\tfor t in [0..1] by .0001\n\t\t\tend = circle t\n\t\t\tl += distance start, end\n\t\t\tstart = end\n\n\t\t#Result length should be approximately 2*pi\n\t\texpect(l).toBeCloseTo 2*Math.PI\n\n"
  },
  {
    "path": "test/tests.sh",
    "content": "#!/usr/bin/env bash\n\n# (requires jasmine-node)\n\njasmine-node --coffee --verbose `dirname $0`/specs\n"
  }
]