import {compose} from '@avocado/core'; import {EventEmitterMixin as EventEmitter} from '../event-emitter'; import {PropertyMixin as Property} from '../property'; import {TransitionMixin as Transition} from '../transition'; const Modulator = { Flat() { return (location) => { return .5; } }, Linear() { return (location) => { return location; } }, Random({variance = .4} = {}) { return (location) => { return Math.abs((Math.random() * (variance + variance) - variance)) % 1; } }, Sine() { return (location) => .5 * (1 + Math.sin(location * Math.PI * 2)) }, }; const decorate = compose( Transition, Property('frequency', { default: 0, }), Property('location', { default: 0, }), Property('magnitude', { default: 0, track: true, }), EventEmitter, ); class ModulatedProperty { constructor( object, key, {frequency, location, magnitude, median, modulators} ) { this.object = object; this.key = key; if (!modulators) { modulators = [Modulator.Linear]; } if (!Array.isArray(modulators)) { modulators = [modulators]; } this.median = median; this.on('magnitudeChanged', this.onMagnitudeChanged, this); this.setFrequency(frequency); this.setLocation(location || 0); this.setMagnitude(magnitude); if (this.median) { this.min = this.median - magnitude; } const modulatorFunction = (modulator) => { if ('string' === typeof modulator) { return Modulator[modulator] ? Modulator[modulator] : Modulator.Linear; } else if ('function' === typeof modulator) { return modulator; } else { return Modulator.Linear; } }; this.modulators = modulators.map((modulator) => { if ('object' !== typeof modulator) { return modulatorFunction(modulator)(modulator); } if (modulator.f) { return modulatorFunction(modulator.f)(modulator); } else { [key] = Object.keys(modulator) return modulatorFunction(key)(modulator[key]); } }); this.transitions = []; } destroy() { this.off('magnitudeChanged', this.onMagnitudeChanged); } onMagnitudeChanged() { this.magnitude2 = this.magnitude() * 2; } tick(elapsed) { this.transitions.forEach((transition) => { transition.tick(elapsed); }); const frequency = this.frequency(); let location = this.location(); location += elapsed; if (location > frequency) { location -= frequency; } this.setLocation(location); const min = this.median ? this.min : this.object[this.key]; const value = this.modulators.reduce( (value, m) => value + m(location / frequency), 0 ) / this.modulators.length; this.object[this.key] = min + value * this.magnitude2; } transition(...args) { const transition = Transition.prototype.transition.apply(this, args); this.transitions.push(transition); transition.promise.then(() => { this.transitions.splice(this.transitions.indexOf(transition), 1); }); return transition; } } export default decorate(ModulatedProperty);