diff --git a/src/framework/math/vec3.d.ts b/src/framework/math/vec3.d.ts index 21b94d9..1fac62b 100644 --- a/src/framework/math/vec3.d.ts +++ b/src/framework/math/vec3.d.ts @@ -34,6 +34,7 @@ export default class vec3 { multiplyByMat3(matrix: mat3, dest?: vec3): vec3; multiplyByQuat(quaternion: quat, dest?: vec3): vec3; toQuat(dest?: quat): quat; + rotateAroundAxis(axis: vec3, angle: number): vec3; static cross(vector: vec3, vector2: vec3, dest?: vec3): vec3; static dot(vector: vec3, vector2: vec3): number; static distance(vector: vec3, vector2: vec3): number; @@ -45,4 +46,7 @@ export default class vec3 { static product(vector: vec3, vector2: vec3, dest?: vec3): vec3; static quotient(vector: vec3, vector2: vec3, dest?: vec3): vec3; static rotate(value: vec3, rotation: quat, dest?: vec3): vec3; + static max(a: vec3, b: vec3): vec3; + static min(a: vec3, b: vec3): vec3; + static lerp(a: vec3, b: vec3, t: number): vec3; } diff --git a/src/framework/math/vec3.js b/src/framework/math/vec3.js index 1181586..5a3482e 100644 --- a/src/framework/math/vec3.js +++ b/src/framework/math/vec3.js @@ -170,6 +170,26 @@ export default class vec3 { dest.w = c.x * c.y * c.z + s.x * s.y * s.z; return dest; } + rotateAroundAxis(axis, angle) { + const normalizedAxis = axis.normalize(); + const sinAngle = Math.sin(angle / 2); + const cosAngle = Math.cos(angle / 2); + // Create a quaternion representing the rotation + const rotationQuat = new quat([ + normalizedAxis.x * sinAngle, + normalizedAxis.y * sinAngle, + normalizedAxis.z * sinAngle, + cosAngle + ]); + // Quaternion for the vector (considering the vector as a quaternion with a w value of 0) + const vecQuat = new quat([this.x, this.y, this.z, 0]); + // Conjugate of the rotation quaternion + const rotationQuatConjugate = rotationQuat.conjugate(); + // Rotate the vector using quaternion multiplication: q * v * q^(-1) + const rotatedVecQuat = rotationQuat.multiply(vecQuat).multiply(rotationQuatConjugate); + // Return the rotated vector + return new vec3([rotatedVecQuat.x, rotatedVecQuat.y, rotatedVecQuat.z]); + } static cross(vector, vector2, dest) { if (!dest) { dest = new vec3(); @@ -283,6 +303,27 @@ export default class vec3 { dest.z = value.z + z * rotation.w + (rotation.x * y - rotation.y * x); return dest; } + static max(a, b) { + return new vec3([ + Math.max(a.x, b.x), + Math.max(a.y, b.y), + Math.max(a.z, b.z), + ]); + } + static min(a, b) { + return new vec3([ + Math.min(a.x, b.x), + Math.min(a.y, b.y), + Math.min(a.z, b.z), + ]); + } + static lerp(a, b, t) { + return new vec3([ + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t, + ]); + } } vec3.zero = new vec3([0, 0, 0]); vec3.one = new vec3([1, 1, 1]); diff --git a/src/framework/resonator/audio-context.js b/src/framework/resonator/audio-context.js index 04d65e5..b471e8c 100644 --- a/src/framework/resonator/audio-context.js +++ b/src/framework/resonator/audio-context.js @@ -27,7 +27,12 @@ export default class ResonatorAudioContext { } decodeAudioData(data) { return __awaiter(this, void 0, void 0, function* () { - return yield this.context.decodeAudioData(data); + try { + return yield this.context.decodeAudioData(data); + } + catch (e) { + console.error(e); + } }); } createPanner() { diff --git a/src/framework/resonator/audio-graph.d.ts b/src/framework/resonator/audio-graph.d.ts index f0f3674..5fa404e 100644 --- a/src/framework/resonator/audio-graph.d.ts +++ b/src/framework/resonator/audio-graph.d.ts @@ -4,8 +4,8 @@ import BaseEffect from './effects/base-effect'; export default class AudioGraph { private master; private effectsBus; - private worldBus; - private secondaryBus; + worldBus: AudioNode; + secondaryBus: AudioNode; private effects; private scene; private context; diff --git a/src/framework/resonator/effects/convolver.d.ts b/src/framework/resonator/effects/convolver.d.ts index be6abb9..73be7b2 100644 --- a/src/framework/resonator/effects/convolver.d.ts +++ b/src/framework/resonator/effects/convolver.d.ts @@ -5,6 +5,10 @@ export default class Convolver extends BaseEffect { private buffer; private channelSplitter; private channelMerger; + private outputGain; + private volume; constructor(context: ResonatorAudioContext, graph: AudioGraph, params: any); + setBuffer(buffer: AudioBuffer): void; + setVolume(volume: number): void; connectInput(node: AudioNode): void; } diff --git a/src/framework/resonator/effects/convolver.js b/src/framework/resonator/effects/convolver.js index 2b798fd..ca053be 100644 --- a/src/framework/resonator/effects/convolver.js +++ b/src/framework/resonator/effects/convolver.js @@ -2,10 +2,24 @@ import BaseEffect from './base-effect'; export default class Convolver extends BaseEffect { constructor(context, graph, params) { super(context, graph, params); + this.volume = 0.25; console.log(`Creating convolver`); this.effectNode = this.context.getContext().createConvolver(); + this.effectNode.normalize = true; this.effectNode.buffer = this.effectParams.buffer; } + setBuffer(buffer) { + this.buffer = buffer; + if (this.effectNode) { + this.effectNode.buffer = buffer; + } + } + setVolume(volume) { + this.volume = volume; + if (this.outputGain) { + this.outputGain.gain.setValueAtTime(this.volume, this.context.getContext().currentTime); + } + } connectInput(node) { this.channelSplitter = this.context.getContext().createChannelSplitter(2); this.channelMerger = this.context.getContext().createChannelMerger(2); @@ -13,8 +27,11 @@ export default class Convolver extends BaseEffect { this.channelSplitter.connect(this.channelMerger, 1, 0); this.channelSplitter.connect(this.channelMerger, 0, 1); this.channelSplitter.connect(this.channelMerger, 1, 1); + this.outputGain = this.context.getContext().createGain(); + this.outputGain.gain.setValueAtTime(this.volume, this.context.getContext().currentTime); node.connect(this.channelSplitter); - this.channelMerger.connect(this.effectNode); + this.channelMerger.connect(this.outputGain); + this.outputGain.connect(this.effectNode); this.inputNode = node; } } diff --git a/src/framework/resonator/index.d.ts b/src/framework/resonator/index.d.ts index a9fd891..4165105 100644 --- a/src/framework/resonator/index.d.ts +++ b/src/framework/resonator/index.d.ts @@ -1,3 +1,5 @@ +import ResonatorScene from './scenes/webaudio-scene'; +import AudioGraph from './audio-graph'; import AudioSource from './sources/audio-source'; import { BaseLoader } from './loaders/base-loader'; import { BaseSource } from './sources/base-source'; @@ -15,8 +17,12 @@ export default class Resonator { loadImmediate(path: string, type?: SourceType): AudioSource; stream(path: string, type?: SourceType): StreamingSource; private createSource; - setEnvironmentImpulse(file: string): Promise; + setEnvironmentImpulse(file: string, volume?: number): Promise; + setEnvironmentImpulseBuffer(file: AudioBuffer, volume?: number): Promise; setListenerPosition(x: number, y: number, z: number): void; setListenerOrientation(forward: any, up: any): void; clearDataPool(): void; + getAudioContext(): AudioContext; + getAudioGraph(): AudioGraph; + getScene(): ResonatorScene; } diff --git a/src/framework/resonator/index.js b/src/framework/resonator/index.js index 260bb81..d8dae71 100644 --- a/src/framework/resonator/index.js +++ b/src/framework/resonator/index.js @@ -51,18 +51,47 @@ export default class Resonator { createSource(type, data) { return new AudioSource(this.graph, this.scene, this.context, data); } - setEnvironmentImpulse(file) { + setEnvironmentImpulse(file, volume = 0.25) { return __awaiter(this, void 0, void 0, function* () { - if (this.environmentImpulse) { - this.graph.removeEffect(this.environmentImpulse); + if (file === null || file === '') { + if (this.environmentImpulse) { + this.graph.removeEffect(this.environmentImpulse); + this.environmentImpulse = null; + } + return; } - if (file === null) { + if (this.environmentImpulse && file !== '') { + const buffer = yield this.dataPool.get(file); + this.environmentImpulse.setBuffer(buffer); + this.environmentImpulse.setVolume(volume); return; } const buffer = yield this.dataPool.get(file); this.environmentImpulse = new Convolver(this.context, this.graph, { buffer }); + this.environmentImpulse.setVolume(volume); + this.graph.applyEffect(this.environmentImpulse); + }); + } + setEnvironmentImpulseBuffer(file, volume = 0.25) { + return __awaiter(this, void 0, void 0, function* () { + if (file === null) { + if (this.environmentImpulse) { + this.graph.removeEffect(this.environmentImpulse); + this.environmentImpulse = null; + } + return; + } + if (this.environmentImpulse && file) { + this.environmentImpulse.setBuffer(file); + this.environmentImpulse.setVolume(volume); + return; + } + this.environmentImpulse = new Convolver(this.context, this.graph, { + buffer: file + }); + this.environmentImpulse.setVolume(volume); this.graph.applyEffect(this.environmentImpulse); }); } @@ -75,4 +104,13 @@ export default class Resonator { clearDataPool() { this.dataPool.clear(); } + getAudioContext() { + return this.context.getContext(); + } + getAudioGraph() { + return this.graph; + } + getScene() { + return this.scene; + } } diff --git a/src/framework/resonator/scenes/webaudio-scene.d.ts b/src/framework/resonator/scenes/webaudio-scene.d.ts index e5ab8b2..2e53a64 100644 --- a/src/framework/resonator/scenes/webaudio-scene.d.ts +++ b/src/framework/resonator/scenes/webaudio-scene.d.ts @@ -1,9 +1,16 @@ -import ResonatorAudioContext from '../audio-context'; -import { EventBus } from '../../event-bus'; +import ResonatorAudioContext from "../audio-context"; +import { EventBus } from "../../event-bus"; export default class ResonatorScene extends EventBus { scene: GainNode; context: ResonatorAudioContext; listener: AudioListener; + position: { + x: number; + y: number; + z: number; + }; + orientation: any; + isFirefox: boolean; constructor(context: ResonatorAudioContext); init(): void; createSource(): any; @@ -11,4 +18,5 @@ export default class ResonatorScene extends EventBus { getInput(): any; setListenerPosition(x: number, y: number, z: number): void; setListenerOrientation(forward: any, rawup: any): void; + private checkIfFirefox; } diff --git a/src/framework/resonator/scenes/webaudio-scene.js b/src/framework/resonator/scenes/webaudio-scene.js index be5704a..cf62917 100644 --- a/src/framework/resonator/scenes/webaudio-scene.js +++ b/src/framework/resonator/scenes/webaudio-scene.js @@ -1,12 +1,20 @@ // The code that deals with 3d audio -import { EventBus } from '../../event-bus'; -import vec3 from '../../math/vec3'; +import { EventBus } from "../../event-bus"; +import vec3 from "../../math/vec3"; export default class ResonatorScene extends EventBus { constructor(context) { super(); + this.position = { x: 0, y: 0, z: 0 }; + this.isFirefox = false; this.context = context; this.scene = this.context.getContext().createGain(); this.listener = this.context.getContext().listener; + this.position = { x: 0, y: 0, z: 0 }; + this.orientation = { + up: { x: 0, y: 1, z: 0 }, + fwd: { x: 0, y: 0, z: -1 }, + }; + this.checkIfFirefox(); this.init(); } init() { @@ -14,8 +22,8 @@ export default class ResonatorScene extends EventBus { } createSource() { const node = this.context.getContext().createPanner(); - node.panningModel = 'HRTF'; - node.distanceModel = 'linear'; + node.panningModel = "HRTF"; + node.distanceModel = "linear"; node.maxDistance = 20; node.refDistance = 2; node.connect(this.scene); @@ -28,7 +36,17 @@ export default class ResonatorScene extends EventBus { return this.scene; } setListenerPosition(x, y, z) { - this.listener.setPosition(x, y, z); + if (x === this.position.x && y === this.position.y && z == this.position.z) + return; + if (this.isFirefox) { + this.listener.setPosition(x, y, z); + } + else { + this.listener.positionX.setValueAtTime(x, this.context.getContext().currentTime); + this.listener.positionY.setValueAtTime(y, this.context.getContext().currentTime); + this.listener.positionZ.setValueAtTime(z, this.context.getContext().currentTime); + } + this.position = { x, y, z }; } setListenerOrientation(forward, rawup) { let fwd = new vec3([forward.x, forward.y, forward.z]); @@ -37,6 +55,25 @@ export default class ResonatorScene extends EventBus { vec3.cross(up, fwd, up); fwd.normalize(); up.normalize(); - this.listener.setOrientation(fwd.x, fwd.y, fwd.z, up.x, up.y, up.z); + if (fwd.x === this.orientation.fwd.x && fwd.y === this.orientation.fwd.y && + fwd.z === this.orientation.fwd.z && up.x === this.orientation.up.x && + up.y === this.orientation.up.y && up.z === this.orientation.up.z) + return; + // this.listener.setOrientation(fwd.x, fwd.y, fwd.z, up.x, up.y, up.z); + if (this.isFirefox) { + this.listener.setOrientation(fwd.x, fwd.y, fwd.z, up.x, up.y, up.z); + } + else { + this.listener.forwardX.setValueAtTime(fwd.x, this.context.getContext().currentTime); + this.listener.forwardY.setValueAtTime(fwd.y, this.context.getContext().currentTime); + this.listener.forwardZ.setValueAtTime(fwd.z, this.context.getContext().currentTime); + this.listener.upX.setValueAtTime(up.x, this.context.getContext().currentTime); + this.listener.upY.setValueAtTime(up.y, this.context.getContext().currentTime); + this.listener.upZ.setValueAtTime(up.z, this.context.getContext().currentTime); + } + this.orientation = { fwd, up }; + } + checkIfFirefox() { + this.isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; } } diff --git a/src/framework/resonator/sources/audio-source.d.ts b/src/framework/resonator/sources/audio-source.d.ts index 15a3c98..d7d8501 100644 --- a/src/framework/resonator/sources/audio-source.d.ts +++ b/src/framework/resonator/sources/audio-source.d.ts @@ -2,6 +2,7 @@ import ResonatorAudioContext from '../audio-context'; import AudioGraph from '../audio-graph'; import ResonatorScene from '../scenes/webaudio-scene'; import { BaseSource } from './base-source'; +import { DistanceModel } from './distance-model'; import { SourceType } from './source-type'; export default class AudioSource implements BaseSource { playing: boolean; @@ -18,6 +19,12 @@ export default class AudioSource implements BaseSource { private volume; private gain; private type; + private distanceModel; + private maxDistance; + private refDistance; + private rollOffFactor; + private filter; + private filterFreq; constructor(graph: AudioGraph, scene: ResonatorScene, context: ResonatorAudioContext, buffer?: AudioBuffer, type?: SourceType); init(): void; getBuffer(): AudioBuffer; @@ -34,4 +41,11 @@ export default class AudioSource implements BaseSource { loop(value: boolean): void; fadeOut(time: number): void; fadeIn(time: number): void; + isPlaying(): boolean; + setDistanceModel(distanceModel: DistanceModel): void; + setMaxDistance(distance: number): void; + setRefDistance(ref: number): void; + setRollOffFactor(factor: number): void; + updateSpatialization(): void; + setFilterFrequency(value: number): void; } diff --git a/src/framework/resonator/sources/audio-source.js b/src/framework/resonator/sources/audio-source.js index 9ac0384..1b5d468 100644 --- a/src/framework/resonator/sources/audio-source.js +++ b/src/framework/resonator/sources/audio-source.js @@ -3,6 +3,7 @@ import { SourceType } from './source-type'; export default class AudioSource { constructor(graph, scene, context, buffer = null, type = SourceType.WorldSource) { + this.filterFreq = 24000; this.position = { x: 0, y: 0, @@ -19,6 +20,10 @@ export default class AudioSource { } init() { this.gain = this.context.createGain(); + this.filter = this.context.getContext().createBiquadFilter(); + this.filter.type = "highshelf"; + this.filter.frequency.value = this.filterFreq; + this.filter.gain.value = -60; // bind methods so we can add and removve them from event listeners this.stop = this.stop.bind(this); } @@ -32,12 +37,15 @@ export default class AudioSource { this.playOnLoad = false; } } - play(when = 0, offset = 0, duration = this.buffer ? this.buffer.duration : 0) { + play(when, offset, duration) { + if (!this.context) + return; if (this.playing && this.node) { this.stop(); } if (!this.buffer) { this.playOnLoad = true; + this.playing = true; return; } if (!this.node) { @@ -47,7 +55,13 @@ export default class AudioSource { } if (this.node) { this.node.playbackRate.value = this.playbackRate; - this.node.start(when, offset, duration); + // Have to do this, otherwise when we pass duration, the node will stop before it can loop. + if (duration) { + this.node.start(when, offset, duration); + } + else { + this.node.start(when, offset); + } this.node.loop = this.looping; this.playing = true; if (this.sceneNode) { @@ -57,6 +71,8 @@ export default class AudioSource { } } setPosition(x, y, z) { + if (x === this.position.x && y === this.position.y && z === this.position.z) + return; this.position = { x, y, @@ -86,8 +102,10 @@ export default class AudioSource { case SourceType.WorldSource: if (!this.sceneNode) { this.sceneNode = this.scene.createSource(); + this.updateSpatialization(); } - this.node.connect(this.gain); + this.node.connect(this.filter); + this.filter.connect(this.gain); this.gain.connect(this.sceneNode); break; case SourceType.UISource: @@ -145,4 +163,41 @@ export default class AudioSource { } this.gain.gain.exponentialRampToValueAtTime(this.volume, this.context.getContext().currentTime + time); } + isPlaying() { + return this.playing; + } + setDistanceModel(distanceModel) { + this.distanceModel = distanceModel; + this.updateSpatialization(); + } + setMaxDistance(distance) { + this.maxDistance = this.maxDistance; + this.updateSpatialization(); + } + setRefDistance(ref) { + this.refDistance = ref; + this.updateSpatialization(); + } + setRollOffFactor(factor) { + this.rollOffFactor = factor; + this.updateSpatialization(); + } + updateSpatialization() { + if (this.sceneNode) { + if (this.distanceModel) + this.sceneNode.distanceModel = this.distanceModel; + if (this.refDistance) + this.sceneNode.refDistance = this.refDistance; + if (this.rollOffFactor) + this.sceneNode.rolloffFactor = this.rollOffFactor; + if (this.maxDistance) + this.sceneNode.maxDistance = this.maxDistance; + } + } + setFilterFrequency(value) { + this.filterFreq = value; + if (this.filter) { + this.filter.frequency.value = this.filterFreq; + } + } } diff --git a/src/framework/resonator/sources/base-source.d.ts b/src/framework/resonator/sources/base-source.d.ts index b325114..226b4e7 100644 --- a/src/framework/resonator/sources/base-source.d.ts +++ b/src/framework/resonator/sources/base-source.d.ts @@ -9,4 +9,5 @@ export interface BaseSource { fadeOut(time: number): void; fadeIn(time: number): void; destroy(): void; + isPlaying(): boolean; } diff --git a/src/framework/resonator/sources/distance-model.d.ts b/src/framework/resonator/sources/distance-model.d.ts new file mode 100644 index 0000000..50e46cf --- /dev/null +++ b/src/framework/resonator/sources/distance-model.d.ts @@ -0,0 +1,5 @@ +export declare enum DistanceModel { + linear = "linear", + inverse = "inverse", + exponential = "exponential" +} diff --git a/src/framework/resonator/sources/distance-model.js b/src/framework/resonator/sources/distance-model.js new file mode 100644 index 0000000..692cb50 --- /dev/null +++ b/src/framework/resonator/sources/distance-model.js @@ -0,0 +1,6 @@ +export var DistanceModel; +(function (DistanceModel) { + DistanceModel["linear"] = "linear"; + DistanceModel["inverse"] = "inverse"; + DistanceModel["exponential"] = "exponential"; +})(DistanceModel || (DistanceModel = {})); diff --git a/src/framework/resonator/sources/streaming-source.d.ts b/src/framework/resonator/sources/streaming-source.d.ts index a8d6527..465d5a9 100644 --- a/src/framework/resonator/sources/streaming-source.d.ts +++ b/src/framework/resonator/sources/streaming-source.d.ts @@ -3,6 +3,7 @@ import AudioGraph from '../audio-graph'; import ResonatorScene from '../scenes/webaudio-scene'; import ResonatorAudioContext from '../audio-context'; import { SourceType } from './source-type'; +import { DistanceModel } from './distance-model'; export declare class StreamingSource implements BaseSource { private graph; private scene; @@ -16,6 +17,12 @@ export declare class StreamingSource implements BaseSource { private sceneNode; private gain; private position; + private distanceModel; + private maxDistance; + private refDistance; + private rollOffFactor; + private filter; + private filterFreq; constructor(graph: AudioGraph, scene: ResonatorScene, context: ResonatorAudioContext, element: HTMLAudioElement, type?: SourceType); private init; play(when?: number, offset?: number, duration?: number): void; @@ -30,4 +37,11 @@ export declare class StreamingSource implements BaseSource { loop(value: boolean): void; fadeIn(time: number): void; fadeOut(time: number): void; + isPlaying(): boolean; + setDistanceModel(distanceModel: DistanceModel): void; + setMaxDistance(distance: number): void; + setRefDistance(ref: number): void; + setRollOffFactor(factor: number): void; + updateSpatialization(): void; + setFilterFrequency(value: number): void; } diff --git a/src/framework/resonator/sources/streaming-source.js b/src/framework/resonator/sources/streaming-source.js index e75889c..2cf40a8 100644 --- a/src/framework/resonator/sources/streaming-source.js +++ b/src/framework/resonator/sources/streaming-source.js @@ -6,6 +6,7 @@ export class StreamingSource { this.context = context; this.element = element; this.type = type; + this.filterFreq = 24000; this.position = { x: 0, y: 0, @@ -15,6 +16,10 @@ export class StreamingSource { } init() { this.node = this.context.createMediaElementSource(this.element); + this.filter = this.context.getContext().createBiquadFilter(); + this.filter.type = "lowshelf"; + this.filter.gain.value = -60; + this.filter.frequency.value = this.filterFreq; this.gain = this.context.createGain(); this.createConnections(); this.element.addEventListener('canplay', (event) => { @@ -50,8 +55,10 @@ export class StreamingSource { case SourceType.WorldSource: if (!this.sceneNode) { this.sceneNode = this.scene.createSource(); + this.updateSpatialization(); } - this.node.connect(this.gain); + this.node.connect(this.filter); + this.filter.connect(this.gain); this.gain.connect(this.sceneNode); break; default: @@ -96,4 +103,37 @@ export class StreamingSource { this.gain.gain.exponentialRampToValueAtTime(0.0001, this.context.getContext().currentTime + time); setTimeout(() => this.stop(), time * 1000); } + isPlaying() { + return this.playing; + } + setDistanceModel(distanceModel) { + this.distanceModel = distanceModel; + this.updateSpatialization(); + } + setMaxDistance(distance) { + this.maxDistance = this.maxDistance; + this.updateSpatialization(); + } + setRefDistance(ref) { + this.refDistance = ref; + this.updateSpatialization(); + } + setRollOffFactor(factor) { + this.rollOffFactor = factor; + this.updateSpatialization(); + } + updateSpatialization() { + if (this.sceneNode) { + this.sceneNode.distanceModel = this.distanceModel; + this.sceneNode.refDistance = this.refDistance; + this.sceneNode.rolloffFactor = this.rollOffFactor; + this.sceneNode.maxDistance = this.maxDistance; + } + } + setFilterFrequency(value) { + this.filterFreq = value; + if (this.filter) { + this.filter.frequency.value = this.filterFreq; + } + } } diff --git a/src/framework/scheduler/raf.d.ts b/src/framework/scheduler/raf.d.ts index d6dfc93..b3ff992 100644 --- a/src/framework/scheduler/raf.d.ts +++ b/src/framework/scheduler/raf.d.ts @@ -1,10 +1,12 @@ import { SchedulerNode } from './node'; export declare class RAFTimer { isStarted: boolean; + private lastTime; + private currTime; node: SchedulerNode; constructor(node: SchedulerNode); start(): void; stop(): void; schedule(): void; - handleResolve(): void; + handleResolve(dt: number): void; } diff --git a/src/framework/scheduler/raf.js b/src/framework/scheduler/raf.js index 75fc4b2..f415478 100644 --- a/src/framework/scheduler/raf.js +++ b/src/framework/scheduler/raf.js @@ -1,5 +1,7 @@ export class RAFTimer { constructor(node) { + this.lastTime = 0; + this.currTime = 0; this.isStarted = false; this.node = node; } @@ -13,9 +15,17 @@ export class RAFTimer { schedule() { window.requestAnimationFrame(this.handleResolve.bind(this)); } - handleResolve() { + handleResolve(dt) { if (this.node) { - this.node.func(1); + if (!this.lastTime) { + this.lastTime = dt; + this.node.func(1); + } + else { + const delta = dt - this.lastTime; + this.lastTime = dt; + this.node.func(delta / 1000); + } if (this.isStarted) { this.schedule(); } diff --git a/src/framework/scheduler/timer.d.ts b/src/framework/scheduler/timer.d.ts index b36b9f8..c62b380 100644 --- a/src/framework/scheduler/timer.d.ts +++ b/src/framework/scheduler/timer.d.ts @@ -10,5 +10,5 @@ export declare class Timer { start(): void; stop(): void; schedule(): void; - handleResolve(): void; + handleResolve(dt: number): void; } diff --git a/src/framework/scheduler/timer.js b/src/framework/scheduler/timer.js index 8c0ec99..922f496 100644 --- a/src/framework/scheduler/timer.js +++ b/src/framework/scheduler/timer.js @@ -3,34 +3,30 @@ export class Timer { this.time = time; this.node = node; this.isStarted = false; + this.fluctuation = 0; } start() { this.isStarted = true; + this.lastTime = performance.now(); this.schedule(); } stop() { if (this.isStarted) { - if (this.intervalID) { - clearTimeout(this.intervalID); - this.intervalID = null; - this.isStarted = false; - } + clearTimeout(this.intervalID); + this.isStarted = false; } } schedule() { - let toWait = this.time; - if (this.lastTime) { - const fluc = Date.now() - this.lastTime; - this.fluctuation = fluc; - toWait -= fluc; - } - this.lastTime = Date.now(); - this.intervalID = setTimeout(this.handleResolve.bind(this), toWait); + const now = performance.now(); + const elapsed = now - this.lastTime; + this.fluctuation = elapsed - this.time; + this.lastTime = now; + const toWait = Math.max(0, this.time - this.fluctuation); + this.intervalID = window.setTimeout(() => this.handleResolve(elapsed), toWait); } - handleResolve() { - this.lastTime = Date.now(); + handleResolve(dt) { if (this.node) { - this.node.func(this.time / this.lastTime); + this.node.func(dt / 1000); // Assuming time is in milliseconds } if (this.isStarted) { this.schedule();