﻿    // Mixin that enables function chaining that facilitates lazy evaluation via lambda-funcs
    published.Walkable =
    {
        wSelect: function(selector) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        var curr;
                        return {
                          reset: function() { walker.reset(); },
                          moveNext: function() {
                            if (!walker.moveNext()) return false;
                            curr = selector(walker.current());
                            return true;
                          },
                          current: function() { return curr; }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wSelect

        wSelectMany: function (e2Walkable) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function () {
                        var walker = srcWalkable.getWalker();
                        var currWalker, curr;
                        return {
                          reset: function () {
                            walker.reset();
                            while (walker.moveNext()) {
                              var walkerCurrent = walker.current();
                              if (walkerCurrent === null) continue;
                              var eWalkable = e2Walkable ? e2Walkable(walkerCurrent) : walkerCurrent;
                              currWalker = eWalkable.getWalker();
                              break;
                            }
                          },

                          moveNext: function () {
                            if (currWalker && currWalker.moveNext()) {
                              curr = currWalker.current();
                              return true;
                            }
                            while (walker.moveNext()) {
                              var walkerCurrent = walker.current();
                              if (walkerCurrent === null) continue;
                              var eWalkable = e2Walkable ? e2Walkable(walkerCurrent) : walkerCurrent;
                              currWalker = eWalkable.getWalker();
                              if (currWalker.moveNext()) {
                                curr = currWalker.current();
                                return true;
                              }
                            }
                            return false;
                          },

                          current: function () { return curr; }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;

        }, //wSelectMany

        wWhere: function(filter) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        return {
                          reset: function() { walker.reset(); },
                          moveNext: function() {
                            while (walker.moveNext()) {
                              var has = filter(walker.current());
                              if (has) return true;
                            }
                            return false;
                          },
                          current: function() { return walker.current(); }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wWhere

        wAt: function(pos) {
              var walker = this.getWalker();
              var idx = -1;
              while(walker.moveNext()) {
                idx++;
                if (idx === pos) return walker.current();
              }

              return null;
        }, //wIdx

        wFirst: function(condition) {
                  var walker = this.getWalker();
                  while(walker.moveNext())
                    if (condition) {
                      if (condition(walker.current()))
                        return walker.current();
                    } else {
                      return walker.current();
                    }

                  return null;
        }, //wFirst

        wFirstIdx: function(condition) {
                    var walker = this.getWalker();
                    var idx = -1;
                    while(walker.moveNext()) {
                      idx++;
                      if (condition(walker.current())) return idx;
                    }
                    return -1;
        }, //wFirstIdx

        wCount: function(condition) {
                var walker = this.getWalker();
                var cnt = 0;
                while(walker.moveNext())
                  if (condition){
                    if (condition(walker.current()))
                      cnt++;
                  } else {
                    cnt++;
                  }
                return cnt;
        }, //wCount

        wDistinct: function(equal) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        function WW()
                        {
                          var existing = [], existingWalkable = WAVE.arrayWalkable(existing);
                          this.reset = function() { walker.reset(); existing.splice(0, existing.length); };

                          function filter(e){
                            if (equal)
                                return equal(e, walker.current());
                            else
                                return e === walker.current();
                          }


                          this.moveNext = function() {
                            while (walker.moveNext()) {
                              if (existingWalkable.wFirstIdx(filter) === -1)
                              {
                                existing.push(walker.current());
                                return true;
                              }
                            }
                            return false;
                          };
                          this.current = function() { return walker.current(); };
                        }
                        return new WW();
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wDistinct

        wConcat: function(other) {
          var srcWalkable = this;

          var walkable = {
            getWalker: function() {
              var walker = srcWalkable.getWalker();
              var walkerOther = null;
              return {
                reset: function() { walker.reset(); walkerOther = null; },
                moveNext: function() {
                  if (walkerOther === null) {
                    if (walker.moveNext()) {
                      return true;
                    } else {
                      walkerOther = other.getWalker();
                    }
                  }
                  return walkerOther.moveNext();
                },
                current: function() { return walkerOther === null ? walker.current() : walkerOther.current(); }
              };
            }
          }; //wConcat

          WAVE.extend(walkable, WAVE.Walkable);
          return walkable;
        },

        wExcept: function(other, equal) {
                    var srcWalkable = this;

                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();

                        function filter(e){
                            if (equal)
                                return equal(walker.current(), e);
                            else
                                return e === walker.current();
                        }

                        return {
                          reset: function() { walker.reset(); },
                          moveNext: function() {
                            while (walker.moveNext()) {
                              if (other.wFirstIdx(filter) === -1)
                              {
                                return true;
                              }
                            }
                            return false;
                          },
                          current: function() { return walker.current(); }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        },//wExcept

        wGroup: function(keyer, valuer) {
                    var srcWalkable=this;
                    var walkable = {
                      getWalker: function() {
                        function WW() {
                          var distinct = srcWalkable.wDistinct(function (a, b) { return keyer(a) === keyer(b); });
                          var distinctWalker = distinct.getWalker();

                          this.reset = function() { distinctWalker.reset(); };
                          this.moveNext = function() { return distinctWalker.moveNext(); };
                          this.current = function() {
                                           return {
                                                    k: keyer(distinctWalker.current()),
                                                    v: srcWalkable.wWhere(function(e) { return keyer(e) === keyer(distinctWalker.current());}).wSelect(function(e) { return valuer(e); })
                                                  };
                                         };
                        }

                        return new WW();
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wGroup

        wGroupIntoObject: function() {
                var obj = {};
                this.wEach(function(e) {   obj[e.k] = e.v.wToArray();   });
                return obj;
        }, //wGroupIntoObject

        wGroupIntoArray: function(keyer, valuer) {
                var arr = [];
                this.wEach(function(e) { arr.push({k: e.k, v: e.v.wToArray()}); } );
                return arr;
        }, //wGroupIntoArray

        wGroupAggregate: function(aggregator, initVal) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        return {
                          reset: function() { walker.reset(); },
                          moveNext: function() { return walker.moveNext(); },
                          current: function() {
                            var cur = walker.current();
                            return {
                              k: cur.k,
                              v: cur.v.wAggregate( function(r, e) { return aggregator(cur.k, r, e); })
                            };
                          }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wGroupAggregate

        wOrder: function(order) {
                var arr = this.wToArray();
                if (!order) order = function(a, b) { return a < b ? -1 : a > b ? 1 : 0; };
                arr.sort(order);
                return WAVE.arrayWalkable(arr);
        }, //wOrder

        wTake: function(qty) {
                    var srcWalkable=this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        function WW() {
                          var taken = 0;
                          this.reset = function() { walker.reset(); taken = 0; };
                          this.moveNext = function() {
                            var has = walker.moveNext() && taken < qty;
                            if (!has) return false;
                            taken++;
                            return true;
                          };
                          this.current = function() { return walker.current(); };
                        }
                        return new WW();
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wTake

        wTakeWhile: function(cond) {
                    var srcWalkable=this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        return {
                          reset: function() { walker.reset(); },
                          moveNext: function() {
                            if (!walker.moveNext()) return false;
                            if (!cond(walker.current())) return false;
                            return true;
                          },
                          current: function() { return walker.current(); }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wTakeWhile

        wSkip: function(qty) {
                    var srcWalkable=this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();

                        function WW() {
                          var idx = -1;
                          this.reset = function() { walker.reset(); idx = -1; };
                          this.moveNext = function() {
                            while(walker.moveNext()) {
                              idx++;
                              if (idx >= qty) return true;
                            }
                            return false;
                          };
                          this.current = function() { return walker.current(); };
                        }

                        return new WW();
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wSkip

        wAny: function(condition) {
                    var walkable = this;
                    var walker = walkable.getWalker();
                    if (!condition) return walker.moveNext();

                    while(walker.moveNext()) if (condition(walker.current())) return true;

                    return false;
        }, //wAny

        wAll: function(condition) {
                    var walkable = this;
                    var walker = walkable.getWalker();
                    if (!condition) return !walker.moveNext();

                    while(walker.moveNext()) if (!condition(walker.current())) return false;

                    return true;
        }, //wAll

        wMin: function(less) {
                    if (!less) less = function(a, b) { return a < b; };

                    var walker = this.getWalker();

                    if (!walker.moveNext()) return null;

                    var minVal = walker.current();

                    while(walker.moveNext()) if (less(walker.current(), minVal)) minVal = walker.current();

                    return minVal;
        }, //wMin

        wMax: function(greater) {
                    if (!greater) greater = function(a, b) { return a > b; };

                    var walker = this.getWalker();

                    if (!walker.moveNext()) return null;

                    var maxVal = walker.current();

                    while(walker.moveNext()) if (greater(walker.current(), maxVal)) maxVal = walker.current();

                    return maxVal;
        }, //wMax

        wAggregate: function(aggregate, initVal) {
                      var r = initVal;
                      this.wEach(function(e) { r = aggregate(r, e); });
                      return r;
        }, //wAggregate

        wSum: function(initVal) {
                      if (typeof(initVal) === tUNDEFINED) initVal = 0;
                      return this.wAggregate(function(r, e) { return r + e; }, initVal);
        }, //wSum

        wEqual: function(other, equalFunc) {
          var selfWalker = this.getWalker();
          var otherWalker = other.getWalker();
          do {
            var selfHasValue = selfWalker.moveNext();
            var otherHasValue = otherWalker.moveNext();

            if (!selfHasValue && !otherHasValue) return true;
            if (selfHasValue !== otherHasValue) return false;

            var selfEl = selfWalker.current();
            var otherEl = otherWalker.current();

            if (!equalFunc) {
              if (selfEl !== otherEl) return false;
            } else {
              if (!equalFunc(selfEl, otherEl)) return false;
            }

          } while(true);
        }, //wEqual

        wToArray: function() {
                    var walkable = this;
                    var walker = walkable.getWalker();
                    var arr = [];
                    while(walker.moveNext()) arr.push(walker.current());

                    return arr;
        }, //wToArray

        wEach: function(action) {
                    var walkable = this;
                    var walker = walkable.getWalker();

                    var i = 0;
                    while(walker.moveNext()) action(walker.current(), i++);

                    return walkable;
        },  //wEach

        // "weight"-based moving average filter (k is kind of "weight" 0..1):
        // if k == 0 - moving object has no weight (hense no momentum) and outbound signal is the same as inbound,
        // if k == 1 - moving object has infinite weight (hense infinity momentum)
        // and outbound signal will be permanent (equal to first sample amplitude)
        wWMA: function(k) {
                    var srcWalkable = this;
                    if (typeof(k) === tUNDEFINED) k = 0.5;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        var prevOutA = null; // previous oubound (processed) amplitude
                        var currOutS = {s: -1, a: 0}; // current outbound (processed) sample

                        return {
                          reset: function() {
                            prevOutA = null;
                            currOutS = {s: -1, a: 0};
                            walker.reset();
                          },

                          moveNext: function() {
                            if (!walker.moveNext()) return false;

                            currOutS.s = walker.current().s;
                            var inA = walker.current().a;

                            if (currOutS.s === 0) {
                              currOutS.a = inA;
                            } else {
                              currOutS.a = (1-k) * inA + k * prevOutA;
                            }

                            prevOutA = currOutS.a;

                            return true;
                          },

                          current: function() { return currOutS; },

                          samplingRate: function() { return walker.samplingRate ? walker.samplingRate() : 2; }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wWMA

        // Frequency filter takes:
        //  "m"(f=ma Newtons law) -  in virtual units of mass (i.e. kilograms) - the higher the number the more attenuation for high frequencies
        //  "k"(f=-kx Hooks law) - in virtual units of spring mimbrane resistance, the higher the number the more attenuation for low frequencies
        wHarmonicFrequencyFilter: function(m,k) {
                    var srcWalkable = this;
                    if (typeof(m) === tUNDEFINED) m = 1;
                    if (typeof(k) === tUNDEFINED) k = 1;
                    if (m<1.0001) m = 1.0001;
                    if (k<0.0001) k = 0.0001;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        var prevOutA = 0; // previous oubound (processed) amplitude
                        var currOutS = {s: -1, a: 0}; // current outbound (processed) sample
                        var v = 0;
                        var prevInA = 0;

                        return {
                          reset: function() {
                            prevOutA = 0;
                            v = 0;
                            prevInA = 0;
                            currOutS = {s: -1, a: 0};
                            walker.reset();
                          },

                          moveNext: function() {
                            if (!walker.moveNext()) return false;

                            currOutS.s = walker.current().s;
                            var inA = walker.current().a;


                            var da = inA - prevInA;
                            prevInA = inA;

                            v += (da - (k * prevOutA)) / m;

                            //console.log(da, v);

                            v*=0.98;//active resistance

                            currOutS.a = prevOutA + v;
                            prevOutA = currOutS.a;

                            return true;
                          },

                          current: function() { return currOutS; },

                          samplingRate: function() { return walker.samplingRate ? walker.samplingRate() : 2; }
                        };
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        }, //wHarmonicFrequencyFilter

        //SA vector definition
        // {s: int, a: float}
        // [s]ample = monotonously increasing integer value
        // [a]mplitude = floating point, the amplitude of the signal

        //Ensures that all {s, a} vectors in incoming walkable are equally spaced in time, so {a} component linearly interpolated between
        //otherwise descret points, i.e. [ {1, -47} , {10, 150} ] => F(2) => yields vectors where
        // respected components (0 -> 5, -47 -> 150)  get linearly pro-rated
        wConstLinearSampling: function(samplingStep) {
                                var srcWalkable = this;
                                var walkable = {
                                  getWalker: function() {
                                    samplingStep = samplingStep || 1;
                                    var walker = srcWalkable.getWalker();
                                    var walkerExausted = false, inSampleLastS = null;
                                    var halfSamplingStep = samplingStep / 2;
                                    var outSampleIdx = 0; // index sample number (natural integer)
                                    var outSampleT; // t of current sample (float point)
                                    var outSampleLeftBorder, outSampleRightBorder; // left and right borders of this sample (float point)
                                    var inSamplesBuf = [];
                                    var curr = {};

                                    function WW() {
                                      this.reset = function() {
                                        inSamplesBuf.splice(0, inSamplesBuf.length);
                                        outSampleIdx = 0;
                                        walkerExausted = false; inSampleLastS = null;
                                        walker.reset();
                                      };

                                      this.moveNext = function() {

                                        function cutBufFromLeft(left, right) {
                                          while(inSamplesBuf.length > 1 && inSamplesBuf[0].s < left && inSamplesBuf[1].s < right) inSamplesBuf.shift();
                                        }

                                        function hasBufNeighbourhoodSamples(left, right) {
                                          for(var i=0; i<inSamplesBuf.length; i++) {
                                            var inSampleS = inSamplesBuf[i].s;
                                            if (inSampleS > left && inSampleS <= right) return true;
                                          }
                                          return false;
                                        }

                                        function walkerMoveNext() {
                                          if (walker.moveNext()) {
                                            inSampleLastS = walker.current().s;
                                            return true;
                                          } else {
                                            walkerExausted = true;
                                            return false;
                                          }
                                        }

                                        function avgNeighbourhoodSamples(left, right) {
                                          var sum = 0, sumQty = 0;
                                          for(var i=0; i<inSamplesBuf.length; i++) {
                                            var inSample = inSamplesBuf[i];
                                            if (inSample.s > left && inSample.s <= right) {
                                              sum += inSample.a;
                                              sumQty++;
                                            }
                                          }
                                          var avg = sum / sumQty;
                                          return avg;
                                        }

                                        function fillBuf(right) {
                                          while(walkerMoveNext()) {
                                            if (walker.current().s >= right) {
                                              inSamplesBuf.push(walker.current());
                                              break;
                                            } else {
                                              inSamplesBuf.push(walker.current());
                                            }
                                          }
                                        }

                                        if (!outSampleIdx) { // first iteration
                                          if (!walkerMoveNext()) return false;

                                          inSamplesBuf.push(walker.current());

                                          outSampleT = walker.current().s;
                                          outSampleLeftBorder = outSampleT - halfSamplingStep;
                                          outSampleRightBorder = outSampleT + halfSamplingStep;

                                          fillBuf(outSampleRightBorder);

                                          curr.s = outSampleIdx++;
                                          var avg = avgNeighbourhoodSamples(-Number.MAX_VALUE, outSampleRightBorder);
                                          curr.a = avg;

                                          return true;
                                        }

                                        outSampleT += samplingStep;
                                        outSampleLeftBorder = outSampleT - halfSamplingStep;
                                        outSampleRightBorder = outSampleT + halfSamplingStep;

                                        if (walkerExausted && inSampleLastS < outSampleLeftBorder) return false;

                                        curr.s = outSampleIdx++;

                                        cutBufFromLeft(outSampleLeftBorder, outSampleRightBorder);

                                        if (hasBufNeighbourhoodSamples(outSampleLeftBorder, outSampleRightBorder)) {
                                          curr.a = avgNeighbourhoodSamples(outSampleLeftBorder, outSampleRightBorder);
                                          return true;
                                        }

                                        fillBuf(outSampleRightBorder);

                                        if (hasBufNeighbourhoodSamples(outSampleLeftBorder, outSampleRightBorder)) {
                                          curr.a = avgNeighbourhoodSamples(outSampleLeftBorder, outSampleRightBorder);
                                          return true;
                                        }

                                        var inSampleLeft = inSamplesBuf[0];
                                        var inSampleRight = inSamplesBuf[1];

                                        var k = (inSampleRight.a - inSampleLeft.a) / (inSampleRight.s - inSampleLeft.s);
                                        var a = inSampleLeft.a - k * inSampleLeft.s;

                                        curr.a = k * outSampleT + a;

                                        return true;
                                      };

                                      this.current = function() {
                                        return curr;//{int sample, float Amplitude}=> {s,a}
                                      };
                                    }

                                    return new WW();
                                  }
                                };

                                WAVE.extend(walkable, WAVE.Walkable);
                                return walkable;
        }, //wConstLinearSampling

        //Outputs {s,a} vectors per SINE function
        wSineGen: function(cfg) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        cfg = cfg || {};
                        var f = cfg.f || 1.0; // Frequency in Hertz assuming that sampling rate is / second
                        var a = typeof(cfg.a) === tUNDEFINED ? 1 : cfg.a; // Amplitude
                        var d = cfg.d || 0; // DC Offset
                        var r = cfg.r || (walker.samplingRate ? walker.samplingRate() : 2); // Sampling Rate in arbitrary units (i.e. seconds)
                        var p = cfg.p || 0; // Phase offset in radians

                        if (r<2) r = 2;//Nyquist frequency

                        var deltaPeriod = (pi2 / r) * f; // the radian cost of 1 period of sine at supplied sampling rate multiplied by desired frequency

                        var sampleIdx = 0; // sample idx (sequental #)
                        var period = p; // current argument
                        var curr = {}; // current sample vector {s,a}

                        walker = srcWalkable.getWalker();
                        function WW() {
                          this.reset = function() { sampleIdx = 0; period = p; walker.reset(); };
                          this.moveNext = function() {
                            if (!walker.moveNext()) return false;
                            curr.s = sampleIdx++;
                            curr.a = (a * Math.sin(period)) + d + walker.current().a;
                            period = geometry.wrapAngle(period, deltaPeriod);
                            return true;
                          };
                          this.current = function() {
                            return curr;//{int sample, float Amplitude}=> {s,a}
                          };

                          this.samplingRate = function() { return r; };
                        }

                        return new WW();
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        },

        //Outputs {s,a} vectors per "Saw" function
        wSawGen: function(cfg) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        cfg = cfg || {};
                        var f = cfg.f || 1.0; // Frequency in Hertz assuming that sampling rate is per second
                        var a = typeof(cfg.a) === tUNDEFINED ? 1 : cfg.a; // Amplitude
                        var d = cfg.d || 0; // DC Offset
                        var r = cfg.r || (walker.samplingRate ? walker.samplingRate() : 2); // Sampling Rate in arbitrary units (i.e. seconds)
                        var p = cfg.p || 0; // Phase offset in radians
                        var s = cfg.s || 0.9; // Symmetry (attack percentage (0..1))

                        if (r<2) r = 2;//Nyquist frequency

                        var k1 = 1 / pi / s;
                        var a1 = -1;

                        var k2 = 1 / pi / (s - 1);
                        var a2 = 1 - 2 * s / (s - 1);

                        var deltaPeriod = (pi2 / r) * f; // the radian cost of 1 period of saw at supplied sampling rate multiplied by desired frequency

                        var sampleIdx = 0; // sample idx (sequental #)
                        var period = p; // current argument
                        var curr = {}; // current sample vector {s,a}

                        walker = srcWalkable.getWalker();
                        function WW() {
                          this.reset = function() { sampleIdx = 0; period = p; walker.reset(); };
                          this.moveNext = function() {
                            if (!walker.moveNext()) return false;
                            curr.s = sampleIdx++;
                            var unitV;
                            if (period < pi2 * s)
                              unitV = k1 * period + a1;
                            else
                              unitV = k2 * period + a2;
                            curr.a = (a * unitV) + d + walker.current().a;
                            period = geometry.wrapAngle(period, deltaPeriod);
                            return true;
                          };

                          this.current = function() { return curr; };

                          this.samplingRate = function() { return r; };
                        }

                        return new WW();
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;

        },

        //Outputs {s,a} vectors per "Square" function
        wSquareGen: function(cfg) {
                    var srcWalkable = this;
                    var walkable = {
                      getWalker: function() {
                        var walker = srcWalkable.getWalker();
                        cfg = cfg || {};
                        var f = cfg.f || 1.0; // Frequency in Hertz assuming that sampling rate is per second
                        var a = typeof(cfg.a) === tUNDEFINED ? 1 : cfg.a; // Amplitude
                        var d = cfg.d || 0; // DC Offset
                        var r = cfg.r || (walker.samplingRate ? walker.samplingRate() : 2); // Sampling Rate in arbitrary units (i.e. seconds)
                        var p = cfg.p || 0; // Phase offset in radians
                        var s = cfg.s || 0.9; // Symmetry (attack percentage (0..1))

                        if (r<2) r = 2;//Nyquist frequency

                        var k1 = -2 / pi / (1 - s);
                        var a1 = 1 - k1 * pi * s;

                        var k2 = 2 / pi / (1 - s);
                        var a2 = -1 - k2 * pi * (1 + s);

                        var deltaPeriod = (pi2 / r) * f; // the radian cost of 1 period of sine at supplied sampling rate multiplied by desired frequency

                        var sampleIdx = 0; // sample idx (sequental #)
                        var period = p; // current argument
                        var curr = {}; // current sample vector {s,a}

                        walker = srcWalkable.getWalker();
                        function WW() {
                          this.reset = function() { sampleIdx = 0; period = p; walker.reset(); };
                          this.moveNext = function() {
                            if (!walker.moveNext()) return false;
                            curr.s = sampleIdx++;
                            var unitV;
                            if(period < pi) {
                              if (period < pi * s) unitV = 1;
                              else unitV = k1 * period + a1;
                            } else {
                              if (period < pi + pi * s) unitV = -1;
                              else unitV = k2 * period + a2;
                            }
                            curr.a = (a * unitV) + d + walker.current().a;
                            period = geometry.wrapAngle(period, deltaPeriod);
                            return true;
                          };

                          this.current = function() { return curr; };

                          this.samplingRate = function() { return r; };
                        }

                        return new WW();
                      }
                    };
                    WAVE.extend(walkable, WAVE.Walkable);
                    return walkable;
        },

        wRandomGen: function(cfg) {
                      var srcWalkable = this;
                      var walkable = {
                        getWalker: function() {
                          var walker = srcWalkable.getWalker();
                          cfg = cfg || {};
                          var a = typeof(cfg.a) === tUNDEFINED ? 1 : cfg.a; // Amplitude
                          var r = cfg.r || (srcWalkable.samplingRate ? srcWalkable.samplingRate() : 2); // Sampling Rate in arbitrary units (i.e. seconds)

                          if (r<2) r = 2;//Nyquist frequency

                          var sampleIdx = 0; // sample idx (sequental #)
                          var curr = {}; // current sample vector {s,a}

                          walker = srcWalkable.getWalker();

                          function WW() {
                            this.reset = function() { walker.reset(); sampleIdx = 0; };
                            this.moveNext = function() {
                              if (!walker.moveNext()) return false;
                              curr.s = sampleIdx++;
                              curr.a = walker.current().a + 2 * a * (Math.random() - 0.5);
                              return true;
                            };
                            this.current = function() { return curr; };
                            this.samplingRate = function() { return r; };
                          }

                          return new WW();
                        }
                      };

                      WAVE.extend(walkable, WAVE.Walkable);
                      return walkable;
        }
    };// Walkable

    // walkable source of constant signal
    published.signalConstSrc = function(conf) {

              function WW(cfg) {
                var a = cfg.a || 0; // Amplitude
                var qty = cfg.qty; // Values count limit (if no value infinite set of values are genereted)
                var r = cfg.r || 2; // Sampling Rate in arbitrary units (i.e. seconds)
                if (r<2) r = 2;//Nyquist frequency

                var i=0;
                var curr = {};

                this.reset = function() { i = 0; };
                this.moveNext = function() {
                  if (qty && i >= qty) return false;

                  curr.s = i;
                  curr.a = a;

                  i++;
                  return true;
                };
                this.current = function() { return curr; };
                this.samplingRate = function() { return r; };
              }

      conf = conf || {};

      var walkable = { getWalker: function() { return new WW(conf); }};
      WAVE.extend(walkable, WAVE.Walkable);

      return walkable;
    };

    // Provides walking capability for array
    published.arrayWalkable = function(arr) {
      var walker = function() {
        var idx = -1;
        this.reset = function() { idx = -1; };
        this.moveNext = function() { return (++idx < arr.length); };
        this.current = function() { return (idx < 0 || idx >= arr.length) ? null : arr[idx]; };
      };

      var walkable = { getWalker: function() { return new walker(); }, OriginalArray: arr };
      WAVE.extend(walkable, WAVE.Walkable);
      return walkable;
    };

    // Converts array with {k : {}, v: []} structure to Walkable group operation result (inverse method to Walkable.wGroupToArray)
    published.groupWalkable = function(arr) {
      var walker = function() {
        var idx = -1;
        var cur = null;
        this.reset = function() { idx = -1; cur = null; };
        this.moveNext = function() {
          if (++idx < arr.length) {
            var arrCur = arr[idx];
            cur = { k: arrCur.k, v: WAVE.arrayWalkable(arrCur.v) };
            return true;
          } else {
            return false;
          }
          //Dkh1222015  return (++idx < arr.length);
        };
        this.current = function() { return cur; };
      };

      var walkable = { getWalker: function() { return new walker(); }, OriginalArray: arr};
      WAVE.extend(walkable, WAVE.Walkable);
      return walkable;
    };