Smooth.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /*
  2. Smooth.js version 0.1.5
  3. Turn arrays into smooth functions.
  4. Copyright 2012 Spencer Cohen
  5. Licensed under MIT license (see "Smooth.js MIT license.txt")
  6. */
  7. /*Constants (these are accessible by Smooth.WHATEVER in user space)
  8. */
  9. (function() {
  10. var AbstractInterpolator, CubicInterpolator, Enum, LinearInterpolator, NearestInterpolator, PI, SincFilterInterpolator, Smooth, clipClamp, clipMirror, clipPeriodic, defaultConfig, getColumn, getType, isValidNumber, k, makeLanczosWindow, makeScaledFunction, makeSincKernel, normalizeScaleTo, root, shallowCopy, sin, sinc, v, validateNumber, validateVector,
  11. __hasProp = Object.prototype.hasOwnProperty,
  12. __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
  13. Enum = {
  14. /*Interpolation methods
  15. */
  16. METHOD_NEAREST: 'nearest',
  17. METHOD_LINEAR: 'linear',
  18. METHOD_CUBIC: 'cubic',
  19. METHOD_LANCZOS: 'lanczos',
  20. METHOD_SINC: 'sinc',
  21. /*Input clipping modes
  22. */
  23. CLIP_CLAMP: 'clamp',
  24. CLIP_ZERO: 'zero',
  25. CLIP_PERIODIC: 'periodic',
  26. CLIP_MIRROR: 'mirror',
  27. /* Constants for control over the cubic interpolation tension
  28. */
  29. CUBIC_TENSION_DEFAULT: 0,
  30. CUBIC_TENSION_CATMULL_ROM: 0
  31. };
  32. defaultConfig = {
  33. method: Enum.METHOD_CUBIC,
  34. cubicTension: Enum.CUBIC_TENSION_DEFAULT,
  35. clip: Enum.CLIP_CLAMP,
  36. scaleTo: 0,
  37. sincFilterSize: 2,
  38. sincWindow: void 0
  39. };
  40. /*Index clipping functions
  41. */
  42. clipClamp = function(i, n) {
  43. return Math.max(0, Math.min(i, n - 1));
  44. };
  45. clipPeriodic = function(i, n) {
  46. i = i % n;
  47. if (i < 0) i += n;
  48. return i;
  49. };
  50. clipMirror = function(i, n) {
  51. var period;
  52. period = 2 * (n - 1);
  53. i = clipPeriodic(i, period);
  54. if (i > n - 1) i = period - i;
  55. return i;
  56. };
  57. /*
  58. Abstract scalar interpolation class which provides common functionality for all interpolators
  59. Subclasses must override interpolate().
  60. */
  61. AbstractInterpolator = (function() {
  62. function AbstractInterpolator(array, config) {
  63. var clipHelpers;
  64. this.array = array.slice(0);
  65. this.length = this.array.length;
  66. clipHelpers = {
  67. clamp: this.clipHelperClamp,
  68. zero: this.clipHelperZero,
  69. periodic: this.clipHelperPeriodic,
  70. mirror: this.clipHelperMirror
  71. };
  72. this.clipHelper = clipHelpers[config.clip];
  73. if (this.clipHelper == null) throw "Invalid clip: " + config.clip;
  74. }
  75. AbstractInterpolator.prototype.getClippedInput = function(i) {
  76. if ((0 <= i && i < this.length)) {
  77. return this.array[i];
  78. } else {
  79. return this.clipHelper(i);
  80. }
  81. };
  82. AbstractInterpolator.prototype.clipHelperClamp = function(i) {
  83. return this.array[clipClamp(i, this.length)];
  84. };
  85. AbstractInterpolator.prototype.clipHelperZero = function(i) {
  86. return 0;
  87. };
  88. AbstractInterpolator.prototype.clipHelperPeriodic = function(i) {
  89. return this.array[clipPeriodic(i, this.length)];
  90. };
  91. AbstractInterpolator.prototype.clipHelperMirror = function(i) {
  92. return this.array[clipMirror(i, this.length)];
  93. };
  94. AbstractInterpolator.prototype.interpolate = function(t) {
  95. throw 'Subclasses of AbstractInterpolator must override the interpolate() method.';
  96. };
  97. return AbstractInterpolator;
  98. })();
  99. NearestInterpolator = (function(_super) {
  100. __extends(NearestInterpolator, _super);
  101. function NearestInterpolator() {
  102. NearestInterpolator.__super__.constructor.apply(this, arguments);
  103. }
  104. NearestInterpolator.prototype.interpolate = function(t) {
  105. return this.getClippedInput(Math.round(t));
  106. };
  107. return NearestInterpolator;
  108. })(AbstractInterpolator);
  109. LinearInterpolator = (function(_super) {
  110. __extends(LinearInterpolator, _super);
  111. function LinearInterpolator() {
  112. LinearInterpolator.__super__.constructor.apply(this, arguments);
  113. }
  114. LinearInterpolator.prototype.interpolate = function(t) {
  115. var a, b, k;
  116. k = Math.floor(t);
  117. a = this.getClippedInput(k);
  118. b = this.getClippedInput(k + 1);
  119. t -= k;
  120. return (1 - t) * a + t * b;
  121. };
  122. return LinearInterpolator;
  123. })(AbstractInterpolator);
  124. CubicInterpolator = (function(_super) {
  125. __extends(CubicInterpolator, _super);
  126. function CubicInterpolator(array, config) {
  127. this.tangentFactor = 1 - Math.max(0, Math.min(1, config.cubicTension));
  128. CubicInterpolator.__super__.constructor.apply(this, arguments);
  129. }
  130. CubicInterpolator.prototype.getTangent = function(k) {
  131. return this.tangentFactor * (this.getClippedInput(k + 1) - this.getClippedInput(k - 1)) / 2;
  132. };
  133. CubicInterpolator.prototype.interpolate = function(t) {
  134. var k, m, p, t2, t3;
  135. k = Math.floor(t);
  136. m = [this.getTangent(k), this.getTangent(k + 1)];
  137. p = [this.getClippedInput(k), this.getClippedInput(k + 1)];
  138. t -= k;
  139. t2 = t * t;
  140. t3 = t * t2;
  141. return (2 * t3 - 3 * t2 + 1) * p[0] + (t3 - 2 * t2 + t) * m[0] + (-2 * t3 + 3 * t2) * p[1] + (t3 - t2) * m[1];
  142. };
  143. return CubicInterpolator;
  144. })(AbstractInterpolator);
  145. sin = Math.sin, PI = Math.PI;
  146. sinc = function(x) {
  147. if (x === 0) {
  148. return 1;
  149. } else {
  150. return sin(PI * x) / (PI * x);
  151. }
  152. };
  153. makeLanczosWindow = function(a) {
  154. return function(x) {
  155. return sinc(x / a);
  156. };
  157. };
  158. makeSincKernel = function(window) {
  159. return function(x) {
  160. return sinc(x) * window(x);
  161. };
  162. };
  163. SincFilterInterpolator = (function(_super) {
  164. __extends(SincFilterInterpolator, _super);
  165. function SincFilterInterpolator(array, config) {
  166. var window;
  167. SincFilterInterpolator.__super__.constructor.apply(this, arguments);
  168. this.a = config.sincFilterSize;
  169. window = config.sincWindow;
  170. if (window == null) throw 'No sincWindow provided';
  171. this.kernel = makeSincKernel(window);
  172. }
  173. SincFilterInterpolator.prototype.interpolate = function(t) {
  174. var k, n, sum, _ref, _ref2;
  175. k = Math.floor(t);
  176. sum = 0;
  177. for (n = _ref = k - this.a + 1, _ref2 = k + this.a; _ref <= _ref2 ? n <= _ref2 : n >= _ref2; _ref <= _ref2 ? n++ : n--) {
  178. sum += this.kernel(t - n) * this.getClippedInput(n);
  179. }
  180. return sum;
  181. };
  182. return SincFilterInterpolator;
  183. })(AbstractInterpolator);
  184. getColumn = function(arr, i) {
  185. var row, _i, _len, _results;
  186. _results = [];
  187. for (_i = 0, _len = arr.length; _i < _len; _i++) {
  188. row = arr[_i];
  189. _results.push(row[i]);
  190. }
  191. return _results;
  192. };
  193. makeScaledFunction = function(f, baseScale, scaleRange) {
  194. var scaleFactor, translation;
  195. if (scaleRange.join === '0,1') {
  196. return f;
  197. } else {
  198. scaleFactor = baseScale / (scaleRange[1] - scaleRange[0]);
  199. translation = scaleRange[0];
  200. return function(t) {
  201. return f(scaleFactor * (t - translation));
  202. };
  203. }
  204. };
  205. getType = function(x) {
  206. return Object.prototype.toString.call(x).slice('[object '.length, -1);
  207. };
  208. validateNumber = function(n) {
  209. if (isNaN(n)) throw 'NaN in Smooth() input';
  210. if (getType(n) !== 'Number') throw 'Non-number in Smooth() input';
  211. if (!isFinite(n)) throw 'Infinity in Smooth() input';
  212. };
  213. validateVector = function(v, dimension) {
  214. var n, _i, _len, _results;
  215. if (getType(v) !== 'Array') throw 'Non-vector in Smooth() input';
  216. if (v.length !== dimension) throw 'Inconsistent dimension in Smooth() input';
  217. _results = [];
  218. for (_i = 0, _len = v.length; _i < _len; _i++) {
  219. n = v[_i];
  220. _results.push(validateNumber(n));
  221. }
  222. return _results;
  223. };
  224. isValidNumber = function(n) {
  225. return (getType(n) === 'Number') && isFinite(n) && !isNaN(n);
  226. };
  227. normalizeScaleTo = function(s) {
  228. var invalidErr;
  229. invalidErr = "scaleTo param must be number or array of two numbers";
  230. switch (getType(s)) {
  231. case 'Number':
  232. if (!isValidNumber(s)) throw invalidErr;
  233. s = [0, s];
  234. break;
  235. case 'Array':
  236. if (s.length !== 2) throw invalidErr;
  237. if (!(isValidNumber(s[0]) && isValidNumber(s[1]))) throw invalidErr;
  238. break;
  239. default:
  240. throw invalidErr;
  241. }
  242. return s;
  243. };
  244. shallowCopy = function(obj) {
  245. var copy, k, v;
  246. copy = {};
  247. for (k in obj) {
  248. if (!__hasProp.call(obj, k)) continue;
  249. v = obj[k];
  250. copy[k] = v;
  251. }
  252. return copy;
  253. };
  254. Smooth = function(arr, config) {
  255. var baseScale, dataType, dimension, i, interpolator, interpolatorClass, interpolatorClasses, interpolators, k, n, scaleRange, smoothFunc, v;
  256. if (config == null) config = {};
  257. config = shallowCopy(config);
  258. if (config.scaleTo == null) config.scaleTo = config.period;
  259. if (config.sincFilterSize == null) {
  260. config.sincFilterSize = config.lanczosFilterSize;
  261. }
  262. for (k in defaultConfig) {
  263. if (!__hasProp.call(defaultConfig, k)) continue;
  264. v = defaultConfig[k];
  265. if (config[k] == null) config[k] = v;
  266. }
  267. interpolatorClasses = {
  268. nearest: NearestInterpolator,
  269. linear: LinearInterpolator,
  270. cubic: CubicInterpolator,
  271. lanczos: SincFilterInterpolator,
  272. sinc: SincFilterInterpolator
  273. };
  274. interpolatorClass = interpolatorClasses[config.method];
  275. if (interpolatorClass == null) throw "Invalid method: " + config.method;
  276. if (config.method === 'lanczos') {
  277. config.sincWindow = makeLanczosWindow(config.sincFilterSize);
  278. }
  279. if (arr.length < 2) throw 'Array must have at least two elements';
  280. dataType = getType(arr[0]);
  281. smoothFunc = (function() {
  282. var _i, _j, _len, _len2;
  283. switch (dataType) {
  284. case 'Number':
  285. if (Smooth.deepValidation) {
  286. for (_i = 0, _len = arr.length; _i < _len; _i++) {
  287. n = arr[_i];
  288. validateNumber(n);
  289. }
  290. }
  291. interpolator = new interpolatorClass(arr, config);
  292. return function(t) {
  293. return interpolator.interpolate(t);
  294. };
  295. case 'Array':
  296. dimension = arr[0].length;
  297. if (!dimension) throw 'Vectors must be non-empty';
  298. if (Smooth.deepValidation) {
  299. for (_j = 0, _len2 = arr.length; _j < _len2; _j++) {
  300. v = arr[_j];
  301. validateVector(v, dimension);
  302. }
  303. }
  304. interpolators = (function() {
  305. var _results;
  306. _results = [];
  307. for (i = 0; 0 <= dimension ? i < dimension : i > dimension; 0 <= dimension ? i++ : i--) {
  308. _results.push(new interpolatorClass(getColumn(arr, i), config));
  309. }
  310. return _results;
  311. })();
  312. return function(t) {
  313. var interpolator, _k, _len3, _results;
  314. _results = [];
  315. for (_k = 0, _len3 = interpolators.length; _k < _len3; _k++) {
  316. interpolator = interpolators[_k];
  317. _results.push(interpolator.interpolate(t));
  318. }
  319. return _results;
  320. };
  321. default:
  322. throw "Invalid element type: " + dataType;
  323. }
  324. })();
  325. if (config.scaleTo) {
  326. scaleRange = normalizeScaleTo(config.scaleTo);
  327. if (config.clip === Smooth.CLIP_PERIODIC) {
  328. baseScale = arr.length;
  329. } else {
  330. baseScale = arr.length - 1;
  331. }
  332. smoothFunc = makeScaledFunction(smoothFunc, baseScale, scaleRange);
  333. }
  334. return smoothFunc;
  335. };
  336. for (k in Enum) {
  337. if (!__hasProp.call(Enum, k)) continue;
  338. v = Enum[k];
  339. Smooth[k] = v;
  340. }
  341. Smooth.deepValidation = true;
  342. root = typeof exports !== "undefined" && exports !== null ? exports : window;
  343. root.Smooth = Smooth;
  344. }).call(this);