Update framework

This commit is contained in:
2022-11-26 02:22:02 +01:00
parent 9a6ce1f832
commit ae057940af
508 changed files with 26011 additions and 14248 deletions

11
framework/resonator/audio-context.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
export default class ResonatorAudioContext {
private context;
constructor();
getContext(): AudioContext;
createGain(): any;
getOutputDestination(): AudioNode;
createBufferSource(): AudioBufferSourceNode;
decodeAudioData(data: ArrayBuffer): Promise<AudioBuffer>;
createPanner(): any;
createMediaElementSource(element: HTMLMediaElement): MediaElementAudioSourceNode;
}

View File

@@ -0,0 +1,39 @@
// simple wrapper around the AudioContext
// eventually will be used to deal with cross browser issues
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
export default class ResonatorAudioContext {
constructor() {
this.context = new AudioContext();
}
getContext() {
return this.context;
}
createGain() {
return this.context.createGain();
}
getOutputDestination() {
return this.context.destination;
}
createBufferSource() {
return this.context.createBufferSource();
}
decodeAudioData(data) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.context.decodeAudioData(data);
});
}
createPanner() {
return this.context.createPanner();
}
createMediaElementSource(element) {
return this.context.createMediaElementSource(element);
}
}

21
framework/resonator/audio-graph.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
import ResonatorScene from './scenes/webaudio-scene';
import ResonatorAudioContext from './audio-context';
import BaseEffect from './effects/base-effect';
export default class AudioGraph {
private master;
private effectsBus;
private worldBus;
private secondaryBus;
private effects;
private scene;
private context;
private swapChannels;
private channelSplitter;
private channelMerger;
constructor(scene: ResonatorScene, context: ResonatorAudioContext, swapChannels?: boolean);
init(): void;
connectToMaster(input: any): void;
connectToUI(input: AudioNode): void;
applyEffect(effect: BaseEffect): void;
removeEffect(effect: BaseEffect): void;
}

View File

@@ -0,0 +1,46 @@
// this is the mixer that takes all the different outputs and mixes them into the 2 busses:
// WorldBus: The directional audio
// SecondaryBus: All the UI and things that are non directional
import EffectChain from './effect-chain';
export default class AudioGraph {
constructor(scene, context, swapChannels = false) {
this.scene = scene;
this.context = context;
this.swapChannels = swapChannels;
this.init();
}
init() {
this.effectsBus = this.context.createGain();
this.worldBus = this.context.createGain();
this.secondaryBus = this.context.createGain();
this.master = this.context.createGain();
this.scene.getOutput().connect(this.worldBus);
// this.worldBus.connect(this.master);
this.worldBus.connect(this.effectsBus);
this.effects = new EffectChain(this.context, this, this.effectsBus, this.master);
this.secondaryBus.connect(this.master);
if (this.swapChannels) {
this.channelSplitter = this.context.getContext().createChannelSplitter(2);
this.channelMerger = this.context.getContext().createChannelMerger(2);
this.master.connect(this.channelSplitter);
this.channelSplitter.connect(this.channelMerger, 0, 1);
this.channelSplitter.connect(this.channelMerger, 1, 0);
this.channelMerger.connect(this.context.getOutputDestination());
}
else {
this.master.connect(this.context.getOutputDestination());
}
}
connectToMaster(input) {
input.connect(this.master);
}
connectToUI(input) {
input.connect(this.secondaryBus);
}
applyEffect(effect) {
this.effects.applyEffect(effect);
}
removeEffect(effect) {
this.effects.removeEffect(effect);
}
}

12
framework/resonator/data-pool-item.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
export default class DataPoolItem {
private name;
private data;
private decodedData;
constructor(name: string, data?: any, decodedData?: any);
getData(): any;
setData(data: any): void;
getDecodedData(): any;
setDecodedData(data: any): void;
getName(): string;
setName(name: string): void;
}

View File

@@ -0,0 +1,26 @@
// An item in the data pool
export default class DataPoolItem {
constructor(name, data = null, decodedData = null) {
this.name = name;
this.data = data;
this.decodedData = decodedData;
}
getData() {
return this.data;
}
setData(data) {
this.data = data;
}
getDecodedData() {
return this.decodedData;
}
setDecodedData(data) {
this.decodedData = this.decodedData;
}
getName() {
return this.name;
}
setName(name) {
this.name = name;
}
}

12
framework/resonator/data-pool.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
import EventEmitter from 'eventemitter3';
import ResonatorAudioContext from './audio-context';
import { BaseLoader } from './loaders/base-loader';
export default class DataPool extends EventEmitter {
private loader;
private data;
private maxData;
private context;
constructor(context: ResonatorAudioContext, loader?: BaseLoader, maxData?: number);
get(path: string): Promise<AudioBuffer>;
clear(): void;
}

View File

@@ -0,0 +1,48 @@
// a data pool holds frequently played sounds in memory together with decoded audio data to no longer have to decode them from the cache when loaded again
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import EventEmitter from 'eventemitter3';
import DataPoolItem from './data-pool-item';
import { HTTPLoader } from './loaders/http-loader';
export default class DataPool extends EventEmitter {
constructor(context, loader = new HTTPLoader(), maxData = 512) {
super();
this.loader = loader;
this.data = {};
this.maxData = maxData;
this.context = context;
}
get(path) {
return __awaiter(this, void 0, void 0, function* () {
if (this.data[path]) {
return this.data[path].getDecodedData();
}
else {
const buffer = yield this.loader.get(path);
const decoded = yield this.context.decodeAudioData(buffer);
const item = new DataPoolItem(path, buffer, decoded);
const length = Object.keys(this.data).length;
if (length < this.maxData) {
this.data[path] = item;
}
else {
// TODO: figure out a more clever solution than just removing the first loaded data. Like tracking how much certain data is needed and prioritize them.
// const paths: string[] = Object.keys(this.data);
// delete this.data[paths[0]];
this.data[path] = item;
}
return item.getDecodedData();
}
});
}
clear() {
this.data = {};
}
}

8
framework/resonator/effect-bus.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
import ResonatorAudioContext from './audio-context';
export default class EffectBus {
private context;
private inputNode;
private channelMerger;
constructor(context: ResonatorAudioContext, input: AudioNode, output: AudioNode);
connect(node: AudioNode): void;
}

View File

@@ -0,0 +1,12 @@
// Currently unused, but eventually all the effect stuff will be moved from audio graph to here to make it easier to work on
export default class EffectBus {
constructor(context, input, output) {
this.context = context;
this.inputNode = input;
this.channelMerger = this.context.getContext().createChannelMerger(1);
this.inputNode.connect(this.channelMerger);
}
connect(node) {
this.channelMerger.connect(node);
}
}

14
framework/resonator/effect-chain.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
import ResonatorAudioContext from './audio-context';
import AudioGraph from './audio-graph';
import BaseEffect from './effects/base-effect';
export default class EffectChain {
private context;
private graph;
private effects;
private inputNode;
private outputNode;
constructor(context: ResonatorAudioContext, graph: AudioGraph, input: AudioNode, output: AudioNode);
applyEffect(effect: BaseEffect): void;
removeEffect(effect: BaseEffect): void;
private updateConnections;
}

View File

@@ -0,0 +1,45 @@
// A chain of effects that connect to the effect bus
export default class EffectChain {
constructor(context, graph, input, output) {
this.effects = [];
this.context = context;
this.graph = graph;
this.inputNode = input;
this.outputNode = output;
this.updateConnections();
}
applyEffect(effect) {
this.effects.push(effect);
this.updateConnections();
}
removeEffect(effect) {
this.effects.forEach((currEffect) => {
if (effect === currEffect) {
currEffect.disconnect();
}
});
this.effects = this.effects.filter((currEffect) => effect !== currEffect);
this.updateConnections();
}
updateConnections() {
if (this.effects.length == 0) {
this.inputNode.connect(this.outputNode);
return;
}
let current = null;
let previous = null;
this.effects.forEach((effect) => {
current = effect;
if (previous) {
current.connectInput(previous.getOutput());
}
else {
current.connectInput(this.inputNode);
}
previous = current;
});
if (current) {
current.connectOutput(this.outputNode);
}
}
}

View File

@@ -0,0 +1,15 @@
import ResonatorAudioContext from '../audio-context';
import AudioGraph from '../audio-graph';
export default class BaseEffect {
protected ready: boolean;
protected effectNode: any;
protected effectParams: any;
protected context: ResonatorAudioContext;
protected graph: AudioGraph;
protected inputNode: AudioNode;
constructor(context: ResonatorAudioContext, graph: AudioGraph, params: any);
connectOutput(node: AudioNode): void;
connectInput(node: AudioNode): void;
getOutput(): AudioNode;
disconnect(): void;
}

View File

@@ -0,0 +1,23 @@
export default class BaseEffect {
constructor(context, graph, params) {
this.graph = graph;
this.context = context;
this.effectParams = params;
}
connectOutput(node) {
this.effectNode.connect(node);
}
connectInput(node) {
this.inputNode = node;
if (this.effectNode) {
this.inputNode.connect(this.effectNode);
}
}
getOutput() {
return this.effectNode;
}
disconnect() {
this.inputNode.disconnect();
this.effectNode.disconnect();
}
}

View File

@@ -0,0 +1,10 @@
import BaseEffect from './base-effect';
import ResonatorAudioContext from '../audio-context';
import AudioGraph from '../audio-graph';
export default class Convolver extends BaseEffect {
private buffer;
private channelSplitter;
private channelMerger;
constructor(context: ResonatorAudioContext, graph: AudioGraph, params: any);
connectInput(node: AudioNode): void;
}

View File

@@ -0,0 +1,20 @@
import BaseEffect from './base-effect';
export default class Convolver extends BaseEffect {
constructor(context, graph, params) {
super(context, graph, params);
console.log(`Creating convolver`);
this.effectNode = this.context.getContext().createConvolver();
this.effectNode.buffer = this.effectParams.buffer;
}
connectInput(node) {
this.channelSplitter = this.context.getContext().createChannelSplitter(2);
this.channelMerger = this.context.getContext().createChannelMerger(2);
this.channelSplitter.connect(this.channelMerger, 0, 0);
this.channelSplitter.connect(this.channelMerger, 1, 0);
this.channelSplitter.connect(this.channelMerger, 0, 1);
this.channelSplitter.connect(this.channelMerger, 1, 1);
node.connect(this.channelSplitter);
this.channelMerger.connect(this.effectNode);
this.inputNode = node;
}
}

22
framework/resonator/index.d.ts vendored Normal file
View File

@@ -0,0 +1,22 @@
import AudioSource from './sources/audio-source';
import { BaseLoader } from './loaders/base-loader';
import { BaseSource } from './sources/base-source';
import { SourceType } from './sources/source-type';
import { StreamingSource } from './sources/streaming-source';
export default class Resonator {
private loader;
private context;
private scene;
private graph;
private dataPool;
private environmentImpulse;
constructor(loader?: BaseLoader);
load(path: string, type?: SourceType): Promise<BaseSource>;
loadImmediate(path: string, type?: SourceType): AudioSource;
stream(path: string, type?: SourceType): StreamingSource;
private createSource;
setEnvironmentImpulse(file: string): Promise<void>;
setListenerPosition(x: number, y: number, z: number): void;
setListenerOrientation(forward: any, up: any): void;
clearDataPool(): void;
}

View File

@@ -0,0 +1,78 @@
// the main module for Resonator
// API, etc.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import ResonatorAudioContext from './audio-context';
import ResonatorScene from './scenes/webaudio-scene';
import AudioGraph from './audio-graph';
import AudioSource from './sources/audio-source';
import DataPool from './data-pool';
import Convolver from './effects/convolver';
import { HTTPLoader } from './loaders/http-loader';
import { SourceType } from './sources/source-type';
import { StreamingSource } from './sources/streaming-source';
export default class Resonator {
constructor(loader = new HTTPLoader()) {
this.loader = loader;
this.environmentImpulse = null;
this.context = new ResonatorAudioContext();
this.scene = new ResonatorScene(this.context);
this.graph = new AudioGraph(this.scene, this.context, false);
this.dataPool = new DataPool(this.context, this.loader);
}
load(path, type = SourceType.WorldSource) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.dataPool.get(path);
const source = this.createSource(type, data);
return source;
});
}
loadImmediate(path, type = SourceType.WorldSource) {
const source = new AudioSource(this.graph, this.scene, this.context, null, type);
this.dataPool.get(path).then((data) => {
source.setBuffer(data);
});
return source;
}
stream(path, type = SourceType.MasterSource) {
const element = new Audio(path);
element.crossOrigin = 'anonymous';
element.volume = 1;
const source = new StreamingSource(this.graph, this.scene, this.context, element, type);
return source;
}
createSource(type, data) {
return new AudioSource(this.graph, this.scene, this.context, data);
}
setEnvironmentImpulse(file) {
return __awaiter(this, void 0, void 0, function* () {
if (this.environmentImpulse) {
this.graph.removeEffect(this.environmentImpulse);
}
if (file === null) {
return;
}
const buffer = yield this.dataPool.get(file);
this.environmentImpulse = new Convolver(this.context, this.graph, {
buffer
});
this.graph.applyEffect(this.environmentImpulse);
});
}
setListenerPosition(x, y, z) {
this.scene.setListenerPosition(x, y, z);
}
setListenerOrientation(forward, up) {
this.scene.setListenerOrientation(forward, up);
}
clearDataPool() {
this.dataPool.clear();
}
}

View File

@@ -0,0 +1,10 @@
import { AssetManager } from '../../asset-manager';
import { BaseLoader } from './base-loader';
export declare class AssetLoader implements BaseLoader {
private name;
private manager;
private assetManager;
constructor(name: string, manager?: AssetManager);
init(): Promise<void>;
get(path: string): Promise<ArrayBuffer>;
}

View File

@@ -0,0 +1,36 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
// TODO fix path when actually properly linking the packages together
import { AssetManager } from '../../asset-manager';
export class AssetLoader {
constructor(name, manager = null) {
this.name = name;
this.manager = manager;
if (manager) {
this.assetManager = manager;
}
else {
this.assetManager = new AssetManager(name, '');
}
}
init() {
return __awaiter(this, void 0, void 0, function* () {
yield this.assetManager.init();
});
}
get(path) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.assetManager.downloadFile(path);
console.log(result);
const buffer = yield result.arrayBuffer();
return buffer;
});
}
}

View File

@@ -0,0 +1,3 @@
export interface BaseLoader {
get(path: string): Promise<ArrayBuffer>;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,4 @@
import { BaseLoader } from './base-loader';
export declare class HTTPLoader implements BaseLoader {
get(path: string): Promise<ArrayBuffer>;
}

View File

@@ -0,0 +1,18 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
export class HTTPLoader {
get(path) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield fetch(path);
const buffer = yield result.arrayBuffer();
return buffer;
});
}
}

View File

@@ -0,0 +1,14 @@
import ResonatorAudioContext from '../audio-context';
import EventEmitter from 'eventemitter3';
export default class Scene extends EventEmitter {
scene: any;
context: ResonatorAudioContext;
listener: AudioListener;
constructor(context: ResonatorAudioContext);
init(): void;
createSource(): any;
getOutput(): any;
getInput(): any;
setListenerPosition(x: number, y: number, z: number): void;
setListenerOrientation(forward: any, rawup: any): void;
}

View File

@@ -0,0 +1,40 @@
// The code that deals with 3d audio
import ResonanceAudio from '../vendor/resonance-es6/main';
import EventEmitter from 'eventemitter3';
import vec3 from '../../tsm/vec3';
export default class Scene extends EventEmitter {
constructor(context) {
super();
this.context = context;
this.scene = new ResonanceAudio(this.context.getContext(), {
ambisonicOrder: 3
});
this.listener = this.context.getContext().listener;
this.init();
}
init() {
// this.scene.output.connect(this.context.getOutputDestination());
}
createSource() {
const source = this.scene.createSource();
return Object.assign(Object.assign({}, source), { getInput: () => source.input });
}
getOutput() {
return this.scene.output;
}
getInput() {
return this.scene.input;
}
setListenerPosition(x, y, z) {
this.scene.setListenerPosition(x, y, z);
}
setListenerOrientation(forward, rawup) {
let fwd = new vec3([forward.x, forward.y, forward.z]);
let up = fwd.copy();
vec3.cross(up, new vec3([rawup.x, rawup.y, rawup.z]), up);
vec3.cross(up, fwd, up);
fwd.normalize();
up.normalize();
this.scene.setListenerOrientation(forward.x, forward.y, forward.z, rawup.x, rawup.y, rawup.z);
}
}

View File

@@ -0,0 +1,14 @@
import ResonatorAudioContext from '../audio-context';
import EventEmitter from 'eventemitter3';
export default class ResonatorScene extends EventEmitter {
scene: GainNode;
context: ResonatorAudioContext;
listener: AudioListener;
constructor(context: ResonatorAudioContext);
init(): void;
createSource(): any;
getOutput(): any;
getInput(): any;
setListenerPosition(x: number, y: number, z: number): void;
setListenerOrientation(forward: any, rawup: any): void;
}

View File

@@ -0,0 +1,42 @@
// The code that deals with 3d audio
import EventEmitter from 'eventemitter3';
import vec3 from '../../tsm/vec3';
export default class ResonatorScene extends EventEmitter {
constructor(context) {
super();
this.context = context;
this.scene = this.context.getContext().createGain();
this.listener = this.context.getContext().listener;
this.init();
}
init() {
// this.scene.output.connect(this.context.getOutputDestination());
}
createSource() {
const node = this.context.getContext().createPanner();
node.panningModel = 'HRTF';
node.distanceModel = 'linear';
node.maxDistance = 20;
node.refDistance = 2;
node.connect(this.scene);
return node;
}
getOutput() {
return this.scene;
}
getInput() {
return this.scene;
}
setListenerPosition(x, y, z) {
this.listener.setPosition(x, y, z);
}
setListenerOrientation(forward, rawup) {
let fwd = new vec3([forward.x, forward.y, forward.z]);
let up = fwd.copy();
vec3.cross(up, new vec3([rawup.x, rawup.y, rawup.z]), up);
vec3.cross(up, fwd, up);
fwd.normalize();
up.normalize();
this.listener.setOrientation(fwd.x, fwd.y, fwd.z, up.x, up.y, up.z);
}
}

View File

@@ -0,0 +1,37 @@
import ResonatorAudioContext from '../audio-context';
import AudioGraph from '../audio-graph';
import ResonatorScene from '../scenes/webaudio-scene';
import { BaseSource } from './base-source';
import { SourceType } from './source-type';
export default class AudioSource implements BaseSource {
playing: boolean;
looping: boolean;
private node;
private sceneNode;
private buffer;
private context;
private graph;
private scene;
private playOnLoad;
private position;
private playbackRate;
private volume;
private gain;
private type;
constructor(graph: AudioGraph, scene: ResonatorScene, context: ResonatorAudioContext, buffer?: AudioBuffer, type?: SourceType);
init(): void;
getBuffer(): AudioBuffer;
setBuffer(data: AudioBuffer): void;
play(when?: number, offset?: number, duration?: number): void;
setPosition(x: number, y: number, z: number): void;
setPlaybackRate(rate: number): void;
getPlaybackRate(): number;
setVolume(volume: number): void;
getVolume(): number;
private createConnections;
stop(): void;
destroy(): void;
loop(value: boolean): void;
fadeOut(time: number): void;
fadeIn(time: number): void;
}

View File

@@ -0,0 +1,148 @@
// an audio source
// This is the actual sound
import { SourceType } from './source-type';
export default class AudioSource {
constructor(graph, scene, context, buffer = null, type = SourceType.WorldSource) {
this.position = {
x: 0,
y: 0,
z: 0
};
this.buffer = buffer;
this.context = context;
this.scene = scene;
this.graph = graph;
this.type = type;
this.playbackRate = 1;
this.volume = 1;
this.init();
}
init() {
this.gain = this.context.createGain();
// bind methods so we can add and removve them from event listeners
this.stop = this.stop.bind(this);
}
getBuffer() {
return this.buffer;
}
setBuffer(data) {
this.buffer = data;
if (this.playOnLoad) {
this.play();
this.playOnLoad = false;
}
}
play(when = 0, offset = 0, duration = this.buffer ? this.buffer.duration : 0) {
if (this.playing && this.node) {
this.stop();
}
if (!this.buffer) {
this.playOnLoad = true;
return;
}
if (!this.node) {
this.node = this.context.createBufferSource();
this.node.buffer = this.buffer;
this.createConnections();
}
if (this.node) {
this.node.playbackRate.value = this.playbackRate;
this.node.start(when, offset, duration);
this.node.loop = this.looping;
this.playing = true;
if (this.sceneNode) {
this.sceneNode.setPosition(this.position.x, this.position.y, this.position.z);
}
this.node.addEventListener('ended', this.stop);
}
}
setPosition(x, y, z) {
this.position = {
x,
y,
z
};
if (this.sceneNode)
this.sceneNode.setPosition(x, y, z);
}
setPlaybackRate(rate) {
this.playbackRate = rate;
if (this.node)
this.node.playbackRate.value = rate;
}
getPlaybackRate() {
return this.playbackRate;
}
setVolume(volume) {
this.volume = volume;
if (this.gain)
this.gain.gain.value = volume;
}
getVolume() {
return this.volume;
}
createConnections() {
switch (this.type) {
case SourceType.WorldSource:
if (!this.sceneNode) {
this.sceneNode = this.scene.createSource();
}
this.node.connect(this.gain);
this.gain.connect(this.sceneNode);
break;
case SourceType.UISource:
this.node.connect(this.gain);
this.graph.connectToUI(this.gain);
break;
default:
this.node.connect(this.gain);
this.graph.connectToMaster(this.gain);
break;
}
}
stop() {
this.playing = false;
if (this.node) {
this.node.removeEventListener('ended', this.stop);
this.node.stop();
this.node.disconnect();
this.node = null;
this.playing = false;
if (this.sceneNode) {
this.sceneNode.disconnect();
this.sceneNode = null;
}
}
}
destroy() {
this.stop();
// set all refs to null to encourage gc
this.node = null;
this.sceneNode = null;
this.buffer = null;
this.context = null;
this.graph = null;
this.scene = null;
}
loop(value) {
this.looping = value;
if (this.node) {
this.node.loop = value;
}
}
fadeOut(time) {
this.gain.gain.setValueAtTime(this.getVolume(), this.context.getContext().currentTime);
if (!this.node) {
return;
}
this.gain.gain.exponentialRampToValueAtTime(0.0001, this.context.getContext().currentTime + time);
setTimeout(() => this.stop(), time * 1000);
}
fadeIn(time) {
this.gain.gain.setValueAtTime(0.0001, this.context.getContext().currentTime);
if (!this.node) {
this.play();
}
this.gain.gain.exponentialRampToValueAtTime(this.volume, this.context.getContext().currentTime + time);
}
}

View File

@@ -0,0 +1,12 @@
export interface BaseSource {
play(when: number, offset: number, duration: number): void;
stop(): void;
setPlaybackRate(value: number): void;
getPlaybackRate(): number;
setVolume(value: number): void;
getVolume(): number;
loop(value: boolean): void;
fadeOut(time: number): void;
fadeIn(time: number): void;
destroy(): void;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,5 @@
export declare enum SourceType {
WorldSource = 0,
UISource = 1,
MasterSource = 2
}

View File

@@ -0,0 +1,6 @@
export var SourceType;
(function (SourceType) {
SourceType[SourceType["WorldSource"] = 0] = "WorldSource";
SourceType[SourceType["UISource"] = 1] = "UISource";
SourceType[SourceType["MasterSource"] = 2] = "MasterSource";
})(SourceType || (SourceType = {}));

View File

@@ -0,0 +1,33 @@
import { BaseSource } from './base-source';
import AudioGraph from '../audio-graph';
import ResonatorScene from '../scenes/webaudio-scene';
import ResonatorAudioContext from '../audio-context';
import { SourceType } from './source-type';
export declare class StreamingSource implements BaseSource {
private graph;
private scene;
private context;
private element;
private type;
playing: boolean;
private playOnAvailable;
private node;
private canPlay;
private sceneNode;
private gain;
private position;
constructor(graph: AudioGraph, scene: ResonatorScene, context: ResonatorAudioContext, element: HTMLAudioElement, type?: SourceType);
private init;
play(when?: number, offset?: number, duration?: number): void;
stop(): void;
getVolume(): number;
setVolume(value: number): void;
getPlaybackRate(): number;
setPlaybackRate(value: number): void;
private createConnections;
setPosition(x: number, y: number, z: number): void;
destroy(): void;
loop(value: boolean): void;
fadeIn(time: number): void;
fadeOut(time: number): void;
}

View File

@@ -0,0 +1,99 @@
import { SourceType } from './source-type';
export class StreamingSource {
constructor(graph, scene, context, element, type = SourceType.MasterSource) {
this.graph = graph;
this.scene = scene;
this.context = context;
this.element = element;
this.type = type;
this.position = {
x: 0,
y: 0,
z: 0
};
this.init();
}
init() {
this.node = this.context.createMediaElementSource(this.element);
this.gain = this.context.createGain();
this.createConnections();
this.element.addEventListener('canplay', (event) => {
this.canPlay = true;
if (this.playOnAvailable) {
this.play();
}
});
}
play(when = 0, offset = 0, duration = 0) {
if (this.canPlay) {
this.element.play();
}
this.playOnAvailable = true;
}
stop() {
this.element.pause();
}
getVolume() {
return this.element.volume;
}
setVolume(value) {
this.element.volume = value;
}
getPlaybackRate() {
return this.element.playbackRate;
}
setPlaybackRate(value) {
this.element.playbackRate = value;
}
createConnections() {
switch (this.type) {
case SourceType.WorldSource:
if (!this.sceneNode) {
this.sceneNode = this.scene.createSource();
}
this.node.connect(this.gain);
this.gain.connect(this.sceneNode);
break;
default:
this.node.connect(this.gain);
this.graph.connectToMaster(this.gain);
break;
}
}
setPosition(x, y, z) {
this.position = {
x,
y,
z
};
if (this.sceneNode)
this.sceneNode.setPosition(x, y, z);
}
destroy() {
this.stop();
this.element = null;
this.graph = null;
this.context = null;
this.node = null;
this.sceneNode = null;
this.scene = null;
}
loop(value) {
this.element.loop = true;
}
fadeIn(time) {
this.gain.gain.setValueAtTime(0.0001, this.context.getContext().currentTime);
if (!this.node) {
this.play();
}
this.gain.gain.exponentialRampToValueAtTime(this.getVolume(), this.context.getContext().currentTime + time);
}
fadeOut(time) {
this.gain.gain.setValueAtTime(this.getVolume(), this.context.getContext().currentTime);
if (!this.node) {
return;
}
this.gain.gain.exponentialRampToValueAtTime(0.0001, this.context.getContext().currentTime + time);
setTimeout(() => this.stop(), time * 1000);
}
}

View File

@@ -0,0 +1,40 @@
export default Attenuation;
/**
* @class Attenuation
* @description Distance-based attenuation filter.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.minDistance
* Min. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MIN_DISTANCE DEFAULT_MIN_DISTANCE}.
* @param {Number} options.maxDistance
* Max. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MAX_DISTANCE DEFAULT_MAX_DISTANCE}.
* @param {string} options.rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}. Defaults to
* {@linkcode Utils.DEFAULT_ATTENUATION_ROLLOFF DEFAULT_ATTENUATION_ROLLOFF}.
*/
declare class Attenuation {
constructor(context: any, options: any);
minDistance: any;
maxDistance: any;
_gainNode: any;
input: any;
output: any;
/**
* Set distance from the listener.
* @param {Number} distance Distance (in meters).
*/
setDistance(distance: number): void;
/**
* Set rolloff.
* @param {string} rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}.
*/
setRolloff(rolloff: string): void;
_rolloff: string;
}

View File

@@ -0,0 +1,151 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Distance-based attenuation filter.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Utils from './utils.js';
/**
* @class Attenuation
* @description Distance-based attenuation filter.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.minDistance
* Min. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MIN_DISTANCE DEFAULT_MIN_DISTANCE}.
* @param {Number} options.maxDistance
* Max. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MAX_DISTANCE DEFAULT_MAX_DISTANCE}.
* @param {string} options.rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}. Defaults to
* {@linkcode Utils.DEFAULT_ATTENUATION_ROLLOFF DEFAULT_ATTENUATION_ROLLOFF}.
*/
class Attenuation {
constructor(context, options) {
// Public variables.
/**
* Min. distance (in meters).
* @member {Number} minDistance
* @memberof Attenuation
* @instance
*/
/**
* Max. distance (in meters).
* @member {Number} maxDistance
* @memberof Attenuation
* @instance
*/
/**
* Mono (1-channel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof Attenuation
* @instance
*/
/**
* Mono (1-channel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof Attenuation
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.minDistance == undefined) {
options.minDistance = Utils.DEFAULT_MIN_DISTANCE;
}
if (options.maxDistance == undefined) {
options.maxDistance = Utils.DEFAULT_MAX_DISTANCE;
}
if (options.rolloff == undefined) {
options.rolloff = Utils.DEFAULT_ATTENUATION_ROLLOFF;
}
// Assign values.
this.minDistance = options.minDistance;
this.maxDistance = options.maxDistance;
this.setRolloff(options.rolloff);
// Create node.
this._gainNode = context.createGain();
// Initialize distance to max distance.
this.setDistance(options.maxDistance);
// Input/Output proxy.
this.input = this._gainNode;
this.output = this._gainNode;
}
/**
* Set distance from the listener.
* @param {Number} distance Distance (in meters).
*/
setDistance(distance) {
let gain = 1;
if (this._rolloff == 'logarithmic') {
if (distance > this.maxDistance) {
gain = 0;
}
else if (distance > this.minDistance) {
let range = this.maxDistance - this.minDistance;
if (range > Utils.EPSILON_FLOAT) {
// Compute the distance attenuation value by the logarithmic curve
// "1 / (d + 1)" with an offset of |minDistance|.
let relativeDistance = distance - this.minDistance;
let attenuation = 1 / (relativeDistance + 1);
let attenuationMax = 1 / (range + 1);
gain = (attenuation - attenuationMax) / (1 - attenuationMax);
}
}
}
else if (this._rolloff == 'linear') {
if (distance > this.maxDistance) {
gain = 0;
}
else if (distance > this.minDistance) {
let range = this.maxDistance - this.minDistance;
if (range > Utils.EPSILON_FLOAT) {
gain = (this.maxDistance - distance) / range;
}
}
}
this._gainNode.gain.value = gain;
}
/**
* Set rolloff.
* @param {string} rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}.
*/
setRolloff(rolloff) {
let isValidModel = ~Utils.ATTENUATION_ROLLOFFS.indexOf(rolloff);
if (rolloff == undefined || !isValidModel) {
if (!isValidModel) {
Utils.log('Invalid rolloff model (\"' + rolloff +
'\"). Using default: \"' + Utils.DEFAULT_ATTENUATION_ROLLOFF + '\".');
}
rolloff = Utils.DEFAULT_ATTENUATION_ROLLOFF;
}
else {
rolloff = rolloff.toString().toLowerCase();
}
this._rolloff = rolloff;
}
}
export default Attenuation;

View File

@@ -0,0 +1,217 @@
declare module 'resonance-audio' {
namespace ResonanceAudio {
/** Options for constructing a new ResonanceAudio scene */
interface Options {
/** Desired ambisonic Order */
ambisonicOrder?: number;
/** The listener's initial position (in meters), where origin is the center of
* the room */
listenerPosition?: Float32Array;
/** The listener's initial forward vector */
listenerForward?: Float32Array;
/** The listener's initial up vector */
listenerUp?: Float32Array;
/** Room dimensions (in meters) */
dimensions?: Utils.RoomDimensions;
/** Named acoustic materials per wall */
materials?: Utils.RoomMaterials;
/** (in meters/second) */
speedOfSound?: number;
}
}
/** Main class for managing sources, room and listener models */
class ResonanceAudio {
/** Binaurally-rendered stereo (2-channel) output */
output: AudioNode;
/** Ambisonic (multichannel) input */
ambisonicInput: AudioNode;
/** Ambisonic (multichannel) output */
ambisonicOutput: AudioNode;
constructor(context: AudioContext, options?: ResonanceAudio.Options);
/**
* Create a new source for the scene.
* @param options
* Options for constructing a new Source.
*/
createSource(options?: Source.Options): Source;
/**
* Set the scene's desired ambisonic order.
* @param ambisonicOrder Desired ambisonic order.
*/
setAmbisonicOrder(ambisonicOrder: any): void;
/**
* Set the room's dimensions and wall materials.
* @param dimensions Room dimensions (in meters).
* @param materials Named acoustic materials per wall.
*/
setRoomProperties(dimensions: Utils.RoomDimensions, materials: Utils.RoomMaterials): void;
/**
* Set the listener's position (in meters), where origin is the center of
* the room.
*/
setListenerPosition(x: number, y: number, z: number): any;
/** Set the source's orientation using forward and up vectors. */
setOrientation(forwardX: number, forwardY: number, forwardZ: number, upX: number, upY: number, upZ: number): void;
/**
* Set the listener's position and orientation using a Three.js Matrix4 object.
* @param matrix
* The Three.js Matrix4 object representing the listener's world transform.
*/
setListenerFromMatrix(matrix4: Float32Array): void;
/**
* Set the speed of sound.
*/
setSpeedOfSound(speedOfSound: number): void;
}
namespace Source {
/** Options for constructing a new Source. */
interface Options {
/** The source's initial position (in meters), where origin is the center of
* the room */
position?: Float32Array;
/** The source's initial forward vector */
forward?: Float32Array;
/** The source's initial up vector */
up?: Float32Array;
/** Min. distance (in meters) */
minDistance?: number;
/** Max. distance (in meters) */
maxDistance?: number;
/** Rolloff model to use */
rolloff?: string;
/** Input gain (linear) */
gain?: number;
/** Directivity alpha */
alpha?: number;
/** Directivity sharpness */
sharpness?: number;
/** Source width (in degrees). Where 0 degrees is a point source and 360 degrees
* is an omnidirectional source */
sourceWidth?: number;
}
}
/**
* Source model to spatialize an audio buffer.
*/
class Source {
constructor(scene: ResonanceAudio, options?: Source.Options);
/** Mono (1-channel) input */
input: AudioNode;
/**
* Set source's position (in meters), where origin is the center of
* the room.
*/
setPosition(x: number, y: number, z: number): void;
/** Set source's rolloff. */
setRolloff(rolloff: string): void;
/** Set source's minimum distance (in meters). */
setMinDistance(minDistance: number): void;
/** Set source's maximum distance (in meters). */
setMaxDistance(maxDistance: number): void;
/** Set source's gain (linear). */
setGain(gain: number): void;
/** Set the source's orientation using forward and up vectors. */
setOrientation(forwardX: number, forwardY: number, forwardZ: number, upX: number, upY: number, upZ: number): void;
/** Set source's position and orientation using a
* Three.js modelViewMatrix object */
setFromMatrix(matrix4: Float32Array): void;
/** Set the source width (in degrees). Where 0 degrees is a point source and 360
* degrees is an omnidirectional source */
setSourceWidth(sourceWidth: number): void;
/**
* Set source's directivity pattern (defined by alpha), where 0 is an
* omnidirectional pattern, 1 is a bidirectional pattern, 0.5 is a cardiod
* pattern. The sharpness of the pattern is increased exponentially
* @param alpha
* Determines directivity pattern (0 to 1).
* @param sharpness
* Determines the sharpness of the directivity pattern (1 to Inf).
*/
setDirectivityPattern(alpha: number, sharpness: number): void;
}
namespace Room {
interface Options {
/** The listener's initial position (in meters), where origin is the center of
* the room */
listenerPosition?: Float32Array;
/** Room dimensions (in meters) */
dimensions?: Utils.RoomDimensions;
/** Named acoustic materials per wall */
materials?: Utils.RoomMaterials;
/** (in meters/second) */
speedOfSound?: number;
}
}
/**
* Model that manages early and late reflections using acoustic
* properties and listener position relative to a rectangular room.
*/
class Room {
constructor(context: AudioContext, options?: Room.Options);
/**
* Set the room's dimensions and wall materials.
* @param dimensions Room dimensions (in meters)
* @param materials Named acoustic materials per wall
*/
setProperties(dimensions: Utils.RoomDimensions, materials: Utils.RoomMaterials): void;
/**
* Set the listener's position (in meters), where origin is the center of
* the room.
*/
setListenerPosition(x: number, y: number, z: number): void;
/**
* Compute distance outside room of provided position (in meters).
* @return
* Distance outside room (in meters). Returns 0 if inside room.
*/
getDistanceOutsideRoom(x: number, y: number, z: number): number;
}
namespace Listener {
interface Options {
/** Desired ambisonic order */
ambisonicOrder: number;
/** Initial position (in meters), where origin is the center of
* the room */
position?: Float32Array;
/** The listener's initial forward vector */
forward?: Float32Array;
/** The listener's initial up vector */
up?: Float32Array;
}
}
/** Listener model to spatialize sources in an environment */
class Listener {
/** Position (in meters) */
position: Float32Array;
/** Ambisonic (multichannel) input */
input: AudioNode;
/** Binaurally-rendered stereo (2-channel) output */
output: AudioNode;
/** Ambisonic (multichannel) output */
ambisonicOutput: AudioNode;
/**
* Set the listener's orientation using forward and up vectors.
*/
setOrientation(forwardX: number, forwardY: number, forwardZ: number, upX: number, upY: number, upZ: number): void;
/** Set listener's position and orientation using a
* Three.js modelViewMatrix object */
setFromMatrix(matrix4: Float32Array): void;
}
namespace Utils {
/** Properties describing the geometry of a room. */
interface RoomDimensions {
width: number;
height: number;
depth: number;
}
/** Properties describing the wall materials */
interface RoomMaterials {
left: string;
right: string;
front: string;
back: string;
down: string;
up: string;
}
}
}

View File

View File

@@ -0,0 +1,47 @@
export default Directivity;
/**
* @class Directivity
* @description Directivity/occlusion filter.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.alpha
* Determines directivity pattern (0 to 1). See
* {@link Directivity#setPattern setPattern} for more details. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_ALPHA DEFAULT_DIRECTIVITY_ALPHA}.
* @param {Number} options.sharpness
* Determines the sharpness of the directivity pattern (1 to Inf). See
* {@link Directivity#setPattern setPattern} for more details. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_SHARPNESS
* DEFAULT_DIRECTIVITY_SHARPNESS}.
*/
declare class Directivity {
constructor(context: any, options: any);
_context: any;
_lowpass: any;
_cosTheta: number;
input: any;
output: any;
/**
* Compute the filter using the source's forward orientation and the listener's
* position.
* @param {Float32Array} forward The source's forward vector.
* @param {Float32Array} direction The direction from the source to the
* listener.
*/
computeAngle(forward: Float32Array, direction: Float32Array): void;
/**
* Set source's directivity pattern (defined by alpha), where 0 is an
* omnidirectional pattern, 1 is a bidirectional pattern, 0.5 is a cardiod
* pattern. The sharpness of the pattern is increased exponenentially.
* @param {Number} alpha
* Determines directivity pattern (0 to 1).
* @param {Number} sharpness
* Determines the sharpness of the directivity pattern (1 to Inf).
* DEFAULT_DIRECTIVITY_SHARPNESS}.
*/
setPattern(alpha: number, sharpness: number): void;
_alpha: number;
_sharpness: number;
}

View File

@@ -0,0 +1,117 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Directivity/occlusion filter.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Utils from './utils.js';
/**
* @class Directivity
* @description Directivity/occlusion filter.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.alpha
* Determines directivity pattern (0 to 1). See
* {@link Directivity#setPattern setPattern} for more details. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_ALPHA DEFAULT_DIRECTIVITY_ALPHA}.
* @param {Number} options.sharpness
* Determines the sharpness of the directivity pattern (1 to Inf). See
* {@link Directivity#setPattern setPattern} for more details. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_SHARPNESS
* DEFAULT_DIRECTIVITY_SHARPNESS}.
*/
class Directivity {
constructor(context, options) {
// Public variables.
/**
* Mono (1-channel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof Directivity
* @instance
*/
/**
* Mono (1-channel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof Directivity
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.alpha == undefined) {
options.alpha = Utils.DEFAULT_DIRECTIVITY_ALPHA;
}
if (options.sharpness == undefined) {
options.sharpness = Utils.DEFAULT_DIRECTIVITY_SHARPNESS;
}
// Create audio node.
this._context = context;
this._lowpass = context.createBiquadFilter();
// Initialize filter coefficients.
this._lowpass.type = 'lowpass';
this._lowpass.Q.value = 0;
this._lowpass.frequency.value = context.sampleRate * 0.5;
this._cosTheta = 0;
this.setPattern(options.alpha, options.sharpness);
// Input/Output proxy.
this.input = this._lowpass;
this.output = this._lowpass;
}
/**
* Compute the filter using the source's forward orientation and the listener's
* position.
* @param {Float32Array} forward The source's forward vector.
* @param {Float32Array} direction The direction from the source to the
* listener.
*/
computeAngle(forward, direction) {
let forwardNorm = Utils.normalizeVector(forward);
let directionNorm = Utils.normalizeVector(direction);
let coeff = 1;
if (this._alpha > Utils.EPSILON_FLOAT) {
let cosTheta = forwardNorm[0] * directionNorm[0] +
forwardNorm[1] * directionNorm[1] + forwardNorm[2] * directionNorm[2];
coeff = (1 - this._alpha) + this._alpha * cosTheta;
coeff = Math.pow(Math.abs(coeff), this._sharpness);
}
this._lowpass.frequency.value = this._context.sampleRate * 0.5 * coeff;
}
/**
* Set source's directivity pattern (defined by alpha), where 0 is an
* omnidirectional pattern, 1 is a bidirectional pattern, 0.5 is a cardiod
* pattern. The sharpness of the pattern is increased exponenentially.
* @param {Number} alpha
* Determines directivity pattern (0 to 1).
* @param {Number} sharpness
* Determines the sharpness of the directivity pattern (1 to Inf).
* DEFAULT_DIRECTIVITY_SHARPNESS}.
*/
setPattern(alpha, sharpness) {
// Clamp and set values.
this._alpha = Math.min(1, Math.max(0, alpha));
this._sharpness = Math.max(1, sharpness);
// Update angle calculation using new values.
this.computeAngle([this._cosTheta * this._cosTheta, 0, 0], [1, 0, 0]);
}
}
export default Directivity;

View File

@@ -0,0 +1,56 @@
export default EarlyReflections;
/**
* @class EarlyReflections
* @description Ray-tracing-based early reflections model.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Utils~RoomDimensions} options.dimensions
* Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Object} options.coefficients
* Frequency-independent reflection coeffs per wall. Defaults to
* {@linkcode Utils.DEFAULT_REFLECTION_COEFFICIENTS
* DEFAULT_REFLECTION_COEFFICIENTS}.
* @param {Number} options.speedOfSound
* (in meters / second). Defaults to {@linkcode Utils.DEFAULT_SPEED_OF_SOUND
* DEFAULT_SPEED_OF_SOUND}.
* @param {Float32Array} options.listenerPosition
* (in meters). Defaults to
* {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
*/
declare class EarlyReflections {
constructor(context: any, options: any);
speedOfSound: any;
input: any;
output: any;
_lowpass: any;
_delays: {};
_gains: {};
_inverters: {};
_merger: any;
_listenerPosition: any;
/**
* Set the listener's position (in meters),
* where [0,0,0] is the center of the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setListenerPosition(x: number, y: number, z: number): void;
/**
* Set the room's properties which determines the characteristics of
* reflections.
* @param {Utils~RoomDimensions} dimensions
* Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Object} coefficients
* Frequency-independent reflection coeffs per wall. Defaults to
* {@linkcode Utils.DEFAULT_REFLECTION_COEFFICIENTS
* DEFAULT_REFLECTION_COEFFICIENTS}.
*/
setRoomProperties(dimensions: any, coefficients: any): void;
_coefficients: any;
_halfDimensions: {};
}

View File

@@ -0,0 +1,212 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Ray-tracing-based early reflections model.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Utils from './utils.js';
/**
* @class EarlyReflections
* @description Ray-tracing-based early reflections model.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Utils~RoomDimensions} options.dimensions
* Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Object} options.coefficients
* Frequency-independent reflection coeffs per wall. Defaults to
* {@linkcode Utils.DEFAULT_REFLECTION_COEFFICIENTS
* DEFAULT_REFLECTION_COEFFICIENTS}.
* @param {Number} options.speedOfSound
* (in meters / second). Defaults to {@linkcode Utils.DEFAULT_SPEED_OF_SOUND
* DEFAULT_SPEED_OF_SOUND}.
* @param {Float32Array} options.listenerPosition
* (in meters). Defaults to
* {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
*/
class EarlyReflections {
constructor(context, options) {
// Public variables.
/**
* The room's speed of sound (in meters/second).
* @member {Number} speedOfSound
* @memberof EarlyReflections
* @instance
*/
/**
* Mono (1-channel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof EarlyReflections
* @instance
*/
/**
* First-order ambisonic (4-channel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof EarlyReflections
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.speedOfSound == undefined) {
options.speedOfSound = Utils.DEFAULT_SPEED_OF_SOUND;
}
if (options.listenerPosition == undefined) {
options.listenerPosition = Utils.DEFAULT_POSITION.slice();
}
if (options.coefficients == undefined) {
options.coefficients = {};
Object.assign(options.coefficients, Utils.DEFAULT_REFLECTION_COEFFICIENTS);
}
// Assign room's speed of sound.
this.speedOfSound = options.speedOfSound;
// Create nodes.
this.input = context.createGain();
this.output = context.createGain();
this._lowpass = context.createBiquadFilter();
this._delays = {};
this._gains = {}; // gainPerWall = (ReflectionCoeff / Attenuation)
this._inverters = {}; // 3 of these are needed for right/back/down walls.
this._merger = context.createChannelMerger(4); // First-order encoding only.
// Connect audio graph for each wall reflection.
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
this._delays[property] =
context.createDelay(Utils.MAX_DURATION);
this._gains[property] = context.createGain();
}
}
this._inverters.right = context.createGain();
this._inverters.down = context.createGain();
this._inverters.back = context.createGain();
// Initialize lowpass filter.
this._lowpass.type = 'lowpass';
this._lowpass.frequency.value = Utils.DEFAULT_REFLECTION_CUTOFF_FREQUENCY;
this._lowpass.Q.value = 0;
// Initialize encoder directions, set delay times and gains to 0.
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
this._delays[property].delayTime.value = 0;
this._gains[property].gain.value = 0;
}
}
// Initialize inverters for opposite walls ('right', 'down', 'back' only).
this._inverters.right.gain.value = -1;
this._inverters.down.gain.value = -1;
this._inverters.back.gain.value = -1;
// Connect nodes.
this.input.connect(this._lowpass);
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
this._lowpass.connect(this._delays[property]);
this._delays[property].connect(this._gains[property]);
this._gains[property].connect(this._merger, 0, 0);
}
}
// Connect gains to ambisonic channel output.
// Left: [1 1 0 0]
// Right: [1 -1 0 0]
// Up: [1 0 1 0]
// Down: [1 0 -1 0]
// Front: [1 0 0 1]
// Back: [1 0 0 -1]
this._gains.left.connect(this._merger, 0, 1);
this._gains.right.connect(this._inverters.right);
this._inverters.right.connect(this._merger, 0, 1);
this._gains.up.connect(this._merger, 0, 2);
this._gains.down.connect(this._inverters.down);
this._inverters.down.connect(this._merger, 0, 2);
this._gains.front.connect(this._merger, 0, 3);
this._gains.back.connect(this._inverters.back);
this._inverters.back.connect(this._merger, 0, 3);
this._merger.connect(this.output);
// Initialize.
this._listenerPosition = options.listenerPosition;
this.setRoomProperties(options.dimensions, options.coefficients);
}
/**
* Set the listener's position (in meters),
* where [0,0,0] is the center of the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setListenerPosition(x, y, z) {
// Assign listener position.
this._listenerPosition = [x, y, z];
// Determine distances to each wall.
let distances = {
left: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.width + x) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
right: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.width - x) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
front: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.depth + z) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
back: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.depth - z) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
down: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.height + y) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
up: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.height - y) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
};
// Assign delay & attenuation values using distances.
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
// Compute and assign delay (in seconds).
let delayInSecs = distances[property] / this.speedOfSound;
this._delays[property].delayTime.value = delayInSecs;
// Compute and assign gain, uses logarithmic rolloff: "g = R / (d + 1)"
let attenuation = this._coefficients[property] / distances[property];
this._gains[property].gain.value = attenuation;
}
}
}
/**
* Set the room's properties which determines the characteristics of
* reflections.
* @param {Utils~RoomDimensions} dimensions
* Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Object} coefficients
* Frequency-independent reflection coeffs per wall. Defaults to
* {@linkcode Utils.DEFAULT_REFLECTION_COEFFICIENTS
* DEFAULT_REFLECTION_COEFFICIENTS}.
*/
setRoomProperties(dimensions, coefficients) {
if (dimensions == undefined) {
dimensions = {};
Object.assign(dimensions, Utils.DEFAULT_ROOM_DIMENSIONS);
}
if (coefficients == undefined) {
coefficients = {};
Object.assign(coefficients, Utils.DEFAULT_REFLECTION_COEFFICIENTS);
}
this._coefficients = coefficients;
// Sanitize dimensions and store half-dimensions.
this._halfDimensions = {};
this._halfDimensions.width = dimensions.width * 0.5;
this._halfDimensions.height = dimensions.height * 0.5;
this._halfDimensions.depth = dimensions.depth * 0.5;
// Update listener position with new room properties.
this.setListenerPosition(this._listenerPosition[0], this._listenerPosition[1], this._listenerPosition[2]);
}
}
export default EarlyReflections;

View File

@@ -0,0 +1,64 @@
export default Encoder;
/**
* @class Encoder
* @description Spatially encodes input using weighted spherical harmonics.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.ambisonicOrder
* Desired ambisonic order. Defaults to
* {@linkcode Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
* @param {Number} options.azimuth
* Azimuth (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_AZIMUTH DEFAULT_AZIMUTH}.
* @param {Number} options.elevation
* Elevation (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_ELEVATION DEFAULT_ELEVATION}.
* @param {Number} options.sourceWidth
* Source width (in degrees). Where 0 degrees is a point source and 360 degrees
* is an omnidirectional source. Defaults to
* {@linkcode Utils.DEFAULT_SOURCE_WIDTH DEFAULT_SOURCE_WIDTH}.
*/
declare class Encoder {
constructor(context: any, options: any);
_context: any;
input: any;
_channelGain: any[];
_merger: any;
output: any;
_azimuth: any;
_elevation: any;
/**
* Set the desired ambisonic order.
* @param {Number} ambisonicOrder Desired ambisonic order.
*/
setAmbisonicOrder(ambisonicOrder: number): void;
_ambisonicOrder: number;
/**
* Set the direction of the encoded source signal.
* @param {Number} azimuth
* Azimuth (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_AZIMUTH DEFAULT_AZIMUTH}.
* @param {Number} elevation
* Elevation (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_ELEVATION DEFAULT_ELEVATION}.
*/
setDirection(azimuth: number, elevation: number): void;
/**
* Set the source width (in degrees). Where 0 degrees is a point source and 360
* degrees is an omnidirectional source.
* @param {Number} sourceWidth (in degrees).
*/
setSourceWidth(sourceWidth: number): void;
_spreadIndex: number;
}
declare namespace Encoder {
/**
* Validate the provided ambisonic order.
* @param {Number} ambisonicOrder Desired ambisonic order.
* @return {Number} Validated/adjusted ambisonic order.
* @private
*/
function validateAmbisonicOrder(ambisonicOrder: number): number;
}

View File

@@ -0,0 +1,194 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Spatially encodes input using weighted spherical harmonics.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Tables from './tables.js';
import Utils from './utils.js';
/**
* @class Encoder
* @description Spatially encodes input using weighted spherical harmonics.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.ambisonicOrder
* Desired ambisonic order. Defaults to
* {@linkcode Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
* @param {Number} options.azimuth
* Azimuth (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_AZIMUTH DEFAULT_AZIMUTH}.
* @param {Number} options.elevation
* Elevation (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_ELEVATION DEFAULT_ELEVATION}.
* @param {Number} options.sourceWidth
* Source width (in degrees). Where 0 degrees is a point source and 360 degrees
* is an omnidirectional source. Defaults to
* {@linkcode Utils.DEFAULT_SOURCE_WIDTH DEFAULT_SOURCE_WIDTH}.
*/
class Encoder {
constructor(context, options) {
// Public variables.
/**
* Mono (1-channel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof Encoder
* @instance
*/
/**
* Ambisonic (multichannel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof Encoder
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.ambisonicOrder == undefined) {
options.ambisonicOrder = Utils.DEFAULT_AMBISONIC_ORDER;
}
if (options.azimuth == undefined) {
options.azimuth = Utils.DEFAULT_AZIMUTH;
}
if (options.elevation == undefined) {
options.elevation = Utils.DEFAULT_ELEVATION;
}
if (options.sourceWidth == undefined) {
options.sourceWidth = Utils.DEFAULT_SOURCE_WIDTH;
}
this._context = context;
// Create I/O nodes.
this.input = context.createGain();
this._channelGain = [];
this._merger = undefined;
this.output = context.createGain();
// Set initial order, angle and source width.
this.setAmbisonicOrder(options.ambisonicOrder);
this._azimuth = options.azimuth;
this._elevation = options.elevation;
this.setSourceWidth(options.sourceWidth);
}
/**
* Set the desired ambisonic order.
* @param {Number} ambisonicOrder Desired ambisonic order.
*/
setAmbisonicOrder(ambisonicOrder) {
this._ambisonicOrder = Encoder.validateAmbisonicOrder(ambisonicOrder);
this.input.disconnect();
for (let i = 0; i < this._channelGain.length; i++) {
this._channelGain[i].disconnect();
}
if (this._merger != undefined) {
this._merger.disconnect();
}
delete this._channelGain;
delete this._merger;
// Create audio graph.
let numChannels = (this._ambisonicOrder + 1) * (this._ambisonicOrder + 1);
this._merger = this._context.createChannelMerger(numChannels);
this._channelGain = new Array(numChannels);
for (let i = 0; i < numChannels; i++) {
this._channelGain[i] = this._context.createGain();
this.input.connect(this._channelGain[i]);
this._channelGain[i].connect(this._merger, 0, i);
}
this._merger.connect(this.output);
}
/**
* Set the direction of the encoded source signal.
* @param {Number} azimuth
* Azimuth (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_AZIMUTH DEFAULT_AZIMUTH}.
* @param {Number} elevation
* Elevation (in degrees). Defaults to
* {@linkcode Utils.DEFAULT_ELEVATION DEFAULT_ELEVATION}.
*/
setDirection(azimuth, elevation) {
// Format input direction to nearest indices.
if (azimuth == undefined || isNaN(azimuth)) {
azimuth = Utils.DEFAULT_AZIMUTH;
}
if (elevation == undefined || isNaN(elevation)) {
elevation = Utils.DEFAULT_ELEVATION;
}
// Store the formatted input (for updating source width).
this._azimuth = azimuth;
this._elevation = elevation;
// Format direction for index lookups.
azimuth = Math.round(azimuth % 360);
if (azimuth < 0) {
azimuth += 360;
}
elevation = Math.round(Math.min(90, Math.max(-90, elevation))) + 90;
// Assign gains to each output.
this._channelGain[0].gain.value = Tables.MAX_RE_WEIGHTS[this._spreadIndex][0];
for (let i = 1; i <= this._ambisonicOrder; i++) {
let degreeWeight = Tables.MAX_RE_WEIGHTS[this._spreadIndex][i];
for (let j = -i; j <= i; j++) {
let acnChannel = (i * i) + i + j;
let elevationIndex = i * (i + 1) / 2 + Math.abs(j) - 1;
let val = Tables.SPHERICAL_HARMONICS[1][elevation][elevationIndex];
if (j != 0) {
let azimuthIndex = Tables.SPHERICAL_HARMONICS_MAX_ORDER + j - 1;
if (j < 0) {
azimuthIndex = Tables.SPHERICAL_HARMONICS_MAX_ORDER + j;
}
val *= Tables.SPHERICAL_HARMONICS[0][azimuth][azimuthIndex];
}
this._channelGain[acnChannel].gain.value = val * degreeWeight;
}
}
}
/**
* Set the source width (in degrees). Where 0 degrees is a point source and 360
* degrees is an omnidirectional source.
* @param {Number} sourceWidth (in degrees).
*/
setSourceWidth(sourceWidth) {
// The MAX_RE_WEIGHTS is a 360 x (Tables.SPHERICAL_HARMONICS_MAX_ORDER+1)
// size table.
this._spreadIndex = Math.min(359, Math.max(0, Math.round(sourceWidth)));
this.setDirection(this._azimuth, this._elevation);
}
}
/**
* Validate the provided ambisonic order.
* @param {Number} ambisonicOrder Desired ambisonic order.
* @return {Number} Validated/adjusted ambisonic order.
* @private
*/
Encoder.validateAmbisonicOrder = ambisonicOrder => {
if (isNaN(ambisonicOrder) || ambisonicOrder == undefined) {
Utils.log('Error: Invalid ambisonic order', options.ambisonicOrder, '\nUsing ambisonicOrder=1 instead.');
ambisonicOrder = 1;
}
else if (ambisonicOrder < 1) {
Utils.log('Error: Unable to render ambisonic order', options.ambisonicOrder, '(Min order is 1)', '\nUsing min order instead.');
ambisonicOrder = 1;
}
else if (ambisonicOrder > Tables.SPHERICAL_HARMONICS_MAX_ORDER) {
Utils.log('Error: Unable to render ambisonic order', options.ambisonicOrder, '(Max order is', Tables.SPHERICAL_HARMONICS_MAX_ORDER, ')\nUsing max order instead.');
options.ambisonicOrder = Tables.SPHERICAL_HARMONICS_MAX_ORDER;
}
return ambisonicOrder;
};
export default Encoder;

View File

@@ -0,0 +1,42 @@
export default LateReflections;
/**
* @class LateReflections
* @description Late-reflections reverberation filter for Ambisonic content.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Array} options.durations
* Multiband RT60 durations (in seconds) for each frequency band, listed as
* {@linkcode Utils.DEFAULT_REVERB_FREQUENCY_BANDS
* FREQUDEFAULT_REVERB_FREQUENCY_BANDSENCY_BANDS}. Defaults to
* {@linkcode Utils.DEFAULT_REVERB_DURATIONS DEFAULT_REVERB_DURATIONS}.
* @param {Number} options.predelay Pre-delay (in milliseconds). Defaults to
* {@linkcode Utils.DEFAULT_REVERB_PREDELAY DEFAULT_REVERB_PREDELAY}.
* @param {Number} options.gain Output gain (linear). Defaults to
* {@linkcode Utils.DEFAULT_REVERB_GAIN DEFAULT_REVERB_GAIN}.
* @param {Number} options.bandwidth Bandwidth (in octaves) for each frequency
* band. Defaults to
* {@linkcode Utils.DEFAULT_REVERB_BANDWIDTH DEFAULT_REVERB_BANDWIDTH}.
* @param {Number} options.tailonset Length (in milliseconds) of impulse
* response to apply a half-Hann window. Defaults to
* {@linkcode Utils.DEFAULT_REVERB_TAIL_ONSET DEFAULT_REVERB_TAIL_ONSET}.
*/
declare class LateReflections {
constructor(context: any, options: any);
_bandwidthCoeff: number;
_tailonsetSamples: number;
_context: any;
input: any;
_predelay: any;
_convolver: any;
output: any;
/**
* Re-compute a new impulse response by providing Multiband RT60 durations.
* @param {Array} durations
* Multiband RT60 durations (in seconds) for each frequency band, listed as
* {@linkcode Utils.DEFAULT_REVERB_FREQUENCY_BANDS
* DEFAULT_REVERB_FREQUENCY_BANDS}.
*/
setDurations(durations: any[]): void;
}

View File

@@ -0,0 +1,187 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Late reverberation filter for Ambisonic content.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Utils from './utils.js';
/**
* @class LateReflections
* @description Late-reflections reverberation filter for Ambisonic content.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Array} options.durations
* Multiband RT60 durations (in seconds) for each frequency band, listed as
* {@linkcode Utils.DEFAULT_REVERB_FREQUENCY_BANDS
* FREQUDEFAULT_REVERB_FREQUENCY_BANDSENCY_BANDS}. Defaults to
* {@linkcode Utils.DEFAULT_REVERB_DURATIONS DEFAULT_REVERB_DURATIONS}.
* @param {Number} options.predelay Pre-delay (in milliseconds). Defaults to
* {@linkcode Utils.DEFAULT_REVERB_PREDELAY DEFAULT_REVERB_PREDELAY}.
* @param {Number} options.gain Output gain (linear). Defaults to
* {@linkcode Utils.DEFAULT_REVERB_GAIN DEFAULT_REVERB_GAIN}.
* @param {Number} options.bandwidth Bandwidth (in octaves) for each frequency
* band. Defaults to
* {@linkcode Utils.DEFAULT_REVERB_BANDWIDTH DEFAULT_REVERB_BANDWIDTH}.
* @param {Number} options.tailonset Length (in milliseconds) of impulse
* response to apply a half-Hann window. Defaults to
* {@linkcode Utils.DEFAULT_REVERB_TAIL_ONSET DEFAULT_REVERB_TAIL_ONSET}.
*/
class LateReflections {
constructor(context, options) {
// Public variables.
/**
* Mono (1-channel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof LateReflections
* @instance
*/
/**
* Mono (1-channel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof LateReflections
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.durations == undefined) {
options.durations = Utils.DEFAULT_REVERB_DURATIONS.slice();
}
if (options.predelay == undefined) {
options.predelay = Utils.DEFAULT_REVERB_PREDELAY;
}
if (options.gain == undefined) {
options.gain = Utils.DEFAULT_REVERB_GAIN;
}
if (options.bandwidth == undefined) {
options.bandwidth = Utils.DEFAULT_REVERB_BANDWIDTH;
}
if (options.tailonset == undefined) {
options.tailonset = Utils.DEFAULT_REVERB_TAIL_ONSET;
}
// Assign pre-computed variables.
let delaySecs = options.predelay / 1000;
this._bandwidthCoeff = options.bandwidth * Utils.LOG2_DIV2;
this._tailonsetSamples = options.tailonset / 1000;
// Create nodes.
this._context = context;
this.input = context.createGain();
this._predelay = context.createDelay(delaySecs);
this._convolver = context.createConvolver();
this.output = context.createGain();
// Set reverb attenuation.
this.output.gain.value = options.gain;
// Disable normalization.
this._convolver.normalize = false;
// Connect nodes.
this.input.connect(this._predelay);
this._predelay.connect(this._convolver);
this._convolver.connect(this.output);
// Compute IR using RT60 values.
this.setDurations(options.durations);
}
/**
* Re-compute a new impulse response by providing Multiband RT60 durations.
* @param {Array} durations
* Multiband RT60 durations (in seconds) for each frequency band, listed as
* {@linkcode Utils.DEFAULT_REVERB_FREQUENCY_BANDS
* DEFAULT_REVERB_FREQUENCY_BANDS}.
*/
setDurations(durations) {
if (durations.length !== Utils.NUMBER_REVERB_FREQUENCY_BANDS) {
Utils.log('Warning: invalid number of RT60 values provided to reverb.');
return;
}
// Compute impulse response.
let durationsSamples = new Float32Array(Utils.NUMBER_REVERB_FREQUENCY_BANDS);
let sampleRate = this._context.sampleRate;
for (let i = 0; i < durations.length; i++) {
// Clamp within suitable range.
durations[i] =
Math.max(0, Math.min(Utils.DEFAULT_REVERB_MAX_DURATION, durations[i]));
// Convert seconds to samples.
durationsSamples[i] = Math.round(durations[i] * sampleRate *
Utils.DEFAULT_REVERB_DURATION_MULTIPLIER);
}
;
// Determine max RT60 length in samples.
let durationsSamplesMax = 0;
for (let i = 0; i < durationsSamples.length; i++) {
if (durationsSamples[i] > durationsSamplesMax) {
durationsSamplesMax = durationsSamples[i];
}
}
// Skip this step if there is no reverberation to compute.
if (durationsSamplesMax < 1) {
durationsSamplesMax = 1;
}
// Create impulse response buffer.
let buffer = this._context.createBuffer(1, durationsSamplesMax, sampleRate);
let bufferData = buffer.getChannelData(0);
// Create noise signal (computed once, referenced in each band's routine).
let noiseSignal = new Float32Array(durationsSamplesMax);
for (let i = 0; i < durationsSamplesMax; i++) {
noiseSignal[i] = Math.random() * 2 - 1;
}
// Compute the decay rate per-band and filter the decaying noise signal.
for (let i = 0; i < Utils.NUMBER_REVERB_FREQUENCY_BANDS; i++) {
// Compute decay rate.
let decayRate = -Utils.LOG1000 / durationsSamples[i];
// Construct a standard one-zero, two-pole bandpass filter:
// H(z) = (b0 * z^0 + b1 * z^-1 + b2 * z^-2) / (1 + a1 * z^-1 + a2 * z^-2)
let omega = Utils.TWO_PI *
Utils.DEFAULT_REVERB_FREQUENCY_BANDS[i] / sampleRate;
let sinOmega = Math.sin(omega);
let alpha = sinOmega * Math.sinh(this._bandwidthCoeff * omega / sinOmega);
let a0CoeffReciprocal = 1 / (1 + alpha);
let b0Coeff = alpha * a0CoeffReciprocal;
let a1Coeff = -2 * Math.cos(omega) * a0CoeffReciprocal;
let a2Coeff = (1 - alpha) * a0CoeffReciprocal;
// We optimize since b2 = -b0, b1 = 0.
// Update equation for two-pole bandpass filter:
// u[n] = x[n] - a1 * x[n-1] - a2 * x[n-2]
// y[n] = b0 * (u[n] - u[n-2])
let um1 = 0;
let um2 = 0;
for (let j = 0; j < durationsSamples[i]; j++) {
// Exponentially-decaying white noise.
let x = noiseSignal[j] * Math.exp(decayRate * j);
// Filter signal with bandpass filter and add to output.
let u = x - a1Coeff * um1 - a2Coeff * um2;
bufferData[j] += b0Coeff * (u - um2);
// Update coefficients.
um2 = um1;
um1 = u;
}
}
// Create and apply half of a Hann window to the beginning of the
// impulse response.
let halfHannLength = Math.round(this._tailonsetSamples);
for (let i = 0; i < Math.min(bufferData.length, halfHannLength); i++) {
let halfHann = 0.5 * (1 - Math.cos(Utils.TWO_PI * i / (2 * halfHannLength - 1)));
bufferData[i] *= halfHann;
}
this._convolver.buffer = buffer;
}
}
export default LateReflections;

View File

@@ -0,0 +1,49 @@
export default Listener;
/**
* @class Listener
* @description Listener model to spatialize sources in an environment.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.ambisonicOrder
* Desired ambisonic order. Defaults to
* {@linkcode Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
* @param {Float32Array} options.position
* Initial position (in meters), where origin is the center of
* the room. Defaults to
* {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @param {Float32Array} options.forward
* The listener's initial forward vector. Defaults to
* {@linkcode Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
* @param {Float32Array} options.up
* The listener's initial up vector. Defaults to
* {@linkcode Utils.DEFAULT_UP DEFAULT_UP}.
*/
declare class Listener {
constructor(context: any, options: any);
position: Float32Array;
_tempMatrix3: Float32Array;
_ambisonicOrder: number;
_context: any;
_renderer: any;
input: any;
output: any;
ambisonicOutput: any;
/**
* Set the source's orientation using forward and up vectors.
* @param {Number} forwardX
* @param {Number} forwardY
* @param {Number} forwardZ
* @param {Number} upX
* @param {Number} upY
* @param {Number} upZ
*/
setOrientation(forwardX: number, forwardY: number, forwardZ: number, upX: number, upY: number, upZ: number): void;
/**
* Set the listener's position and orientation using a Three.js Matrix4 object.
* @param {Object} matrix4
* The Three.js Matrix4 object representing the listener's world transform.
*/
setFromMatrix(matrix4: any): void;
}

View File

@@ -0,0 +1,168 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Listener model to spatialize sources in an environment.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Omnitone from 'omnitone/build/omnitone.esm';
import Encoder from './encoder.js';
import Utils from './utils.js';
/**
* @class Listener
* @description Listener model to spatialize sources in an environment.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Number} options.ambisonicOrder
* Desired ambisonic order. Defaults to
* {@linkcode Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
* @param {Float32Array} options.position
* Initial position (in meters), where origin is the center of
* the room. Defaults to
* {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @param {Float32Array} options.forward
* The listener's initial forward vector. Defaults to
* {@linkcode Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
* @param {Float32Array} options.up
* The listener's initial up vector. Defaults to
* {@linkcode Utils.DEFAULT_UP DEFAULT_UP}.
*/
class Listener {
constructor(context, options) {
// Public variables.
/**
* Position (in meters).
* @member {Float32Array} position
* @memberof Listener
* @instance
*/
/**
* Ambisonic (multichannel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof Listener
* @instance
*/
/**
* Binaurally-rendered stereo (2-channel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof Listener
* @instance
*/
/**
* Ambisonic (multichannel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} ambisonicOutput
* @memberof Listener
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.ambisonicOrder == undefined) {
options.ambisonicOrder = Utils.DEFAULT_AMBISONIC_ORDER;
}
if (options.position == undefined) {
options.position = Utils.DEFAULT_POSITION.slice();
}
if (options.forward == undefined) {
options.forward = Utils.DEFAULT_FORWARD.slice();
}
if (options.up == undefined) {
options.up = Utils.DEFAULT_UP.slice();
}
// Member variables.
this.position = new Float32Array(3);
this._tempMatrix3 = new Float32Array(9);
// Select the appropriate HRIR filters using 2-channel chunks since
// multichannel audio is not yet supported by a majority of browsers.
this._ambisonicOrder =
Encoder.validateAmbisonicOrder(options.ambisonicOrder);
// Create audio nodes.
this._context = context;
if (this._ambisonicOrder == 1) {
this._renderer = Omnitone.createFOARenderer(context, {});
}
else if (this._ambisonicOrder > 1) {
this._renderer = Omnitone.createHOARenderer(context, {
ambisonicOrder: this._ambisonicOrder,
});
}
// These nodes are created in order to safely asynchronously load Omnitone
// while the rest of the scene is being created.
this.input = context.createGain();
this.output = context.createGain();
this.ambisonicOutput = context.createGain();
// Initialize Omnitone (async) and connect to audio graph when complete.
let that = this;
this._renderer.initialize().then(() => {
// Connect pre-rotated soundfield to renderer.
that.input.connect(that._renderer.input);
// Connect rotated soundfield to ambisonic output.
if (that._ambisonicOrder > 1) {
that._renderer._hoaRotator.output.connect(that.ambisonicOutput);
}
else {
that._renderer._foaRotator.output.connect(that.ambisonicOutput);
}
// Connect binaurally-rendered soundfield to binaural output.
that._renderer.output.connect(that.output);
});
// Set orientation and update rotation matrix accordingly.
this.setOrientation(options.forward[0], options.forward[1], options.forward[2], options.up[0], options.up[1], options.up[2]);
}
/**
* Set the source's orientation using forward and up vectors.
* @param {Number} forwardX
* @param {Number} forwardY
* @param {Number} forwardZ
* @param {Number} upX
* @param {Number} upY
* @param {Number} upZ
*/
setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ) {
let right = Utils.crossProduct([forwardX, forwardY, forwardZ], [upX, upY, upZ]);
this._tempMatrix3[0] = right[0];
this._tempMatrix3[1] = right[1];
this._tempMatrix3[2] = right[2];
this._tempMatrix3[3] = upX;
this._tempMatrix3[4] = upY;
this._tempMatrix3[5] = upZ;
this._tempMatrix3[6] = forwardX;
this._tempMatrix3[7] = forwardY;
this._tempMatrix3[8] = forwardZ;
this._renderer.setRotationMatrix3(this._tempMatrix3);
}
/**
* Set the listener's position and orientation using a Three.js Matrix4 object.
* @param {Object} matrix4
* The Three.js Matrix4 object representing the listener's world transform.
*/
setFromMatrix(matrix4) {
// Update ambisonic rotation matrix internally.
this._renderer.setRotationMatrix4(matrix4.elements);
// Extract position from matrix.
this.position[0] = matrix4.elements[12];
this.position[1] = matrix4.elements[13];
this.position[2] = matrix4.elements[14];
}
}
export default Listener;

View File

@@ -0,0 +1,2 @@
export default ResonanceAudio;
import ResonanceAudio from "./resonance-audio";

View File

@@ -0,0 +1,23 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Primary namespace for ResonanceAudio library.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
import ResonanceAudio from './resonance-audio';
// Main module.
export default ResonanceAudio;

View File

@@ -0,0 +1,130 @@
export default ResonanceAudio;
/**
* ~ResonanceAudioOptions
*/
export type ResonanceAudio = {
/**
* Desired ambisonic Order. Defaults to
* {@link Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
*/
ambisonicOrder: number;
/**
* The listener's initial position (in meters), where origin is the center of
* the room. Defaults to {@link Utils.DEFAULT_POSITION DEFAULT_POSITION}.
*/
listenerPosition: Float32Array;
/**
* The listener's initial forward vector.
* Defaults to {@link Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
*/
listenerForward: Float32Array;
/**
* The listener's initial up vector.
* Defaults to {@link Utils.DEFAULT_UP DEFAULT_UP}.
*/
listenerUp: Float32Array;
/**
* ~RoomDimensions} dimensions Room dimensions (in meters). Defaults to
* {@link Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
*/
"": Utils;
/**
* (in meters/second). Defaults to
* {@link Utils.DEFAULT_SPEED_OF_SOUND DEFAULT_SPEED_OF_SOUND}.
*/
speedOfSound: number;
};
/**
* Options for constructing a new ResonanceAudio scene.
* @typedef {Object} ResonanceAudio~ResonanceAudioOptions
* @property {Number} ambisonicOrder
* Desired ambisonic Order. Defaults to
* {@linkcode Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
* @property {Float32Array} listenerPosition
* The listener's initial position (in meters), where origin is the center of
* the room. Defaults to {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @property {Float32Array} listenerForward
* The listener's initial forward vector.
* Defaults to {@linkcode Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
* @property {Float32Array} listenerUp
* The listener's initial up vector.
* Defaults to {@linkcode Utils.DEFAULT_UP DEFAULT_UP}.
* @property {Utils~RoomDimensions} dimensions Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @property {Utils~RoomMaterials} materials Named acoustic materials per wall.
* Defaults to {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
* @property {Number} speedOfSound
* (in meters/second). Defaults to
* {@linkcode Utils.DEFAULT_SPEED_OF_SOUND DEFAULT_SPEED_OF_SOUND}.
*/
/**
* @class ResonanceAudio
* @description Main class for managing sources, room and listener models.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {ResonanceAudio~ResonanceAudioOptions} options
* Options for constructing a new ResonanceAudio scene.
*/
declare class ResonanceAudio {
constructor(context: any, options: any);
_ambisonicOrder: number;
_sources: any[];
_room: Room;
_listener: Listener;
_context: any;
output: any;
ambisonicOutput: any;
ambisonicInput: any;
/**
* Create a new source for the scene.
* @param {Source~SourceOptions} options
* Options for constructing a new Source.
* @return {Source}
*/
createSource(options: any): Source;
/**
* Set the scene's desired ambisonic order.
* @param {Number} ambisonicOrder Desired ambisonic order.
*/
setAmbisonicOrder(ambisonicOrder: number): void;
/**
* Set the room's dimensions and wall materials.
* @param {Object} dimensions Room dimensions (in meters).
* @param {Object} materials Named acoustic materials per wall.
*/
setRoomProperties(dimensions: any, materials: any): void;
/**
* Set the listener's position (in meters), where origin is the center of
* the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setListenerPosition(x: number, y: number, z: number): void;
/**
* Set the source's orientation using forward and up vectors.
* @param {Number} forwardX
* @param {Number} forwardY
* @param {Number} forwardZ
* @param {Number} upX
* @param {Number} upY
* @param {Number} upZ
*/
setListenerOrientation(forwardX: number, forwardY: number, forwardZ: number, upX: number, upY: number, upZ: number): void;
/**
* Set the listener's position and orientation using a Three.js Matrix4 object.
* @param {Object} matrix
* The Three.js Matrix4 object representing the listener's world transform.
*/
setListenerFromMatrix(matrix: any): void;
/**
* Set the speed of sound.
* @param {Number} speedOfSound
*/
setSpeedOfSound(speedOfSound: number): void;
}
import Utils from "./utils.js";
import Room from "./room.js";
import Listener from "./listener.js";
import Source from "./source.js";

View File

@@ -0,0 +1,213 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file ResonanceAudio library name space and common utilities.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Listener from './listener.js';
import Source from './source.js';
import Room from './room.js';
import Encoder from './encoder.js';
import Utils from './utils.js';
/**
* Options for constructing a new ResonanceAudio scene.
* @typedef {Object} ResonanceAudio~ResonanceAudioOptions
* @property {Number} ambisonicOrder
* Desired ambisonic Order. Defaults to
* {@linkcode Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
* @property {Float32Array} listenerPosition
* The listener's initial position (in meters), where origin is the center of
* the room. Defaults to {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @property {Float32Array} listenerForward
* The listener's initial forward vector.
* Defaults to {@linkcode Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
* @property {Float32Array} listenerUp
* The listener's initial up vector.
* Defaults to {@linkcode Utils.DEFAULT_UP DEFAULT_UP}.
* @property {Utils~RoomDimensions} dimensions Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @property {Utils~RoomMaterials} materials Named acoustic materials per wall.
* Defaults to {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
* @property {Number} speedOfSound
* (in meters/second). Defaults to
* {@linkcode Utils.DEFAULT_SPEED_OF_SOUND DEFAULT_SPEED_OF_SOUND}.
*/
/**
* @class ResonanceAudio
* @description Main class for managing sources, room and listener models.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {ResonanceAudio~ResonanceAudioOptions} options
* Options for constructing a new ResonanceAudio scene.
*/
class ResonanceAudio {
constructor(context, options) {
// Public variables.
/**
* Binaurally-rendered stereo (2-channel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof ResonanceAudio
* @instance
*/
/**
* Ambisonic (multichannel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}
* (For rendering input soundfields).
* @member {AudioNode} ambisonicInput
* @memberof ResonanceAudio
* @instance
*/
/**
* Ambisonic (multichannel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}
* (For allowing external rendering / post-processing).
* @member {AudioNode} ambisonicOutput
* @memberof ResonanceAudio
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.ambisonicOrder == undefined) {
options.ambisonicOrder = Utils.DEFAULT_AMBISONIC_ORDER;
}
if (options.listenerPosition == undefined) {
options.listenerPosition = Utils.DEFAULT_POSITION.slice();
}
if (options.listenerForward == undefined) {
options.listenerForward = Utils.DEFAULT_FORWARD.slice();
}
if (options.listenerUp == undefined) {
options.listenerUp = Utils.DEFAULT_UP.slice();
}
if (options.dimensions == undefined) {
options.dimensions = {};
Object.assign(options.dimensions, Utils.DEFAULT_ROOM_DIMENSIONS);
}
if (options.materials == undefined) {
options.materials = {};
Object.assign(options.materials, Utils.DEFAULT_ROOM_MATERIALS);
}
if (options.speedOfSound == undefined) {
options.speedOfSound = Utils.DEFAULT_SPEED_OF_SOUND;
}
// Create member submodules.
this._ambisonicOrder = Encoder.validateAmbisonicOrder(options.ambisonicOrder);
this._sources = [];
this._room = new Room(context, {
listenerPosition: options.listenerPosition,
dimensions: options.dimensions,
materials: options.materials,
speedOfSound: options.speedOfSound,
});
this._listener = new Listener(context, {
ambisonicOrder: options.ambisonicOrder,
position: options.listenerPosition,
forward: options.listenerForward,
up: options.listenerUp,
});
// Create auxillary audio nodes.
this._context = context;
this.output = context.createGain();
this.ambisonicOutput = context.createGain();
this.ambisonicInput = this._listener.input;
// Connect audio graph.
this._room.output.connect(this._listener.input);
this._listener.output.connect(this.output);
this._listener.ambisonicOutput.connect(this.ambisonicOutput);
}
/**
* Create a new source for the scene.
* @param {Source~SourceOptions} options
* Options for constructing a new Source.
* @return {Source}
*/
createSource(options) {
// Create a source and push it to the internal sources array, returning
// the object's reference to the user.
let source = new Source(this, options);
this._sources[this._sources.length] = source;
return source;
}
/**
* Set the scene's desired ambisonic order.
* @param {Number} ambisonicOrder Desired ambisonic order.
*/
setAmbisonicOrder(ambisonicOrder) {
this._ambisonicOrder = Encoder.validateAmbisonicOrder(ambisonicOrder);
}
/**
* Set the room's dimensions and wall materials.
* @param {Object} dimensions Room dimensions (in meters).
* @param {Object} materials Named acoustic materials per wall.
*/
setRoomProperties(dimensions, materials) {
this._room.setProperties(dimensions, materials);
}
/**
* Set the listener's position (in meters), where origin is the center of
* the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setListenerPosition(x, y, z) {
// Update listener position.
this._listener.position[0] = x;
this._listener.position[1] = y;
this._listener.position[2] = z;
this._room.setListenerPosition(x, y, z);
// Update sources with new listener position.
this._sources.forEach(element => {
element._update();
});
}
/**
* Set the source's orientation using forward and up vectors.
* @param {Number} forwardX
* @param {Number} forwardY
* @param {Number} forwardZ
* @param {Number} upX
* @param {Number} upY
* @param {Number} upZ
*/
setListenerOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ) {
this._listener.setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ);
}
/**
* Set the listener's position and orientation using a Three.js Matrix4 object.
* @param {Object} matrix
* The Three.js Matrix4 object representing the listener's world transform.
*/
setListenerFromMatrix(matrix) {
this._listener.setFromMatrix(matrix);
// Update the rest of the scene using new listener position.
this.setListenerPosition(this._listener.position[0], this._listener.position[1], this._listener.position[2]);
}
/**
* Set the speed of sound.
* @param {Number} speedOfSound
*/
setSpeedOfSound(speedOfSound) {
this._room.speedOfSound = speedOfSound;
}
}
export default ResonanceAudio;

View File

@@ -0,0 +1,55 @@
export default Room;
/**
* @class Room
* @description Model that manages early and late reflections using acoustic
* properties and listener position relative to a rectangular room.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Float32Array} options.listenerPosition
* The listener's initial position (in meters), where origin is the center of
* the room. Defaults to {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @param {Utils~RoomDimensions} options.dimensions Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Utils~RoomMaterials} options.materials Named acoustic materials per wall.
* Defaults to {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
* @param {Number} options.speedOfSound
* (in meters/second). Defaults to
* {@linkcode Utils.DEFAULT_SPEED_OF_SOUND DEFAULT_SPEED_OF_SOUND}.
*/
declare class Room {
constructor(context: any, options: any);
early: EarlyReflections;
late: LateReflections;
speedOfSound: any;
output: any;
_merger: any;
/**
* Set the room's dimensions and wall materials.
* @param {Utils~RoomDimensions} dimensions Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Utils~RoomMaterials} materials Named acoustic materials per wall. Defaults to
* {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
*/
setProperties(dimensions: any, materials: any): void;
/**
* Set the listener's position (in meters), where origin is the center of
* the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setListenerPosition(x: number, y: number, z: number): void;
/**
* Compute distance outside room of provided position (in meters).
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @return {Number}
* Distance outside room (in meters). Returns 0 if inside room.
*/
getDistanceOutsideRoom(x: number, y: number, z: number): number;
}
import EarlyReflections from "./early-reflections.js";
import LateReflections from "./late-reflections.js";

View File

@@ -0,0 +1,300 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Complete room model with early and late reflections.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import LateReflections from './late-reflections.js';
import EarlyReflections from './early-reflections.js';
import Utils from './utils.js';
/**
* Generate absorption coefficients from material names.
* @param {Object} materials
* @return {Object}
*/
function _getCoefficientsFromMaterials(materials) {
// Initialize coefficients to use defaults.
let coefficients = {};
for (let property in Utils.DEFAULT_ROOM_MATERIALS) {
if (Utils.DEFAULT_ROOM_MATERIALS.hasOwnProperty(property)) {
coefficients[property] = Utils.ROOM_MATERIAL_COEFFICIENTS[Utils.DEFAULT_ROOM_MATERIALS[property]];
}
}
// Sanitize materials.
if (materials == undefined) {
materials = {};
Object.assign(materials, Utils.DEFAULT_ROOM_MATERIALS);
}
// Assign coefficients using provided materials.
for (let property in Utils.DEFAULT_ROOM_MATERIALS) {
if (Utils.DEFAULT_ROOM_MATERIALS.hasOwnProperty(property) &&
materials.hasOwnProperty(property)) {
if (materials[property] in Utils.ROOM_MATERIAL_COEFFICIENTS) {
coefficients[property] =
Utils.ROOM_MATERIAL_COEFFICIENTS[materials[property]];
}
else {
Utils.log('Material \"' + materials[property] + '\" on wall \"' +
property + '\" not found. Using \"' +
Utils.DEFAULT_ROOM_MATERIALS[property] + '\".');
}
}
else {
Utils.log('Wall \"' + property + '\" is not defined. Default used.');
}
}
return coefficients;
}
/**
* Sanitize coefficients.
* @param {Object} coefficients
* @return {Object}
*/
function _sanitizeCoefficients(coefficients) {
if (coefficients == undefined) {
coefficients = {};
}
for (let property in Utils.DEFAULT_ROOM_MATERIALS) {
if (!(coefficients.hasOwnProperty(property))) {
// If element is not present, use default coefficients.
coefficients[property] = Utils.ROOM_MATERIAL_COEFFICIENTS[Utils.DEFAULT_ROOM_MATERIALS[property]];
}
}
return coefficients;
}
/**
* Sanitize dimensions.
* @param {Utils~RoomDimensions} dimensions
* @return {Utils~RoomDimensions}
*/
function _sanitizeDimensions(dimensions) {
if (dimensions == undefined) {
dimensions = {};
}
for (let property in Utils.DEFAULT_ROOM_DIMENSIONS) {
if (!(dimensions.hasOwnProperty(property))) {
dimensions[property] = Utils.DEFAULT_ROOM_DIMENSIONS[property];
}
}
return dimensions;
}
/**
* Compute frequency-dependent reverb durations.
* @param {Utils~RoomDimensions} dimensions
* @param {Object} coefficients
* @param {Number} speedOfSound
* @return {Array}
*/
function _getDurationsFromProperties(dimensions, coefficients, speedOfSound) {
let durations = new Float32Array(Utils.NUMBER_REVERB_FREQUENCY_BANDS);
// Sanitize inputs.
dimensions = _sanitizeDimensions(dimensions);
coefficients = _sanitizeCoefficients(coefficients);
if (speedOfSound == undefined) {
speedOfSound = Utils.DEFAULT_SPEED_OF_SOUND;
}
// Acoustic constant.
let k = Utils.TWENTY_FOUR_LOG10 / speedOfSound;
// Compute volume, skip if room is not present.
let volume = dimensions.width * dimensions.height * dimensions.depth;
if (volume < Utils.ROOM_MIN_VOLUME) {
return durations;
}
// Room surface area.
let leftRightArea = dimensions.width * dimensions.height;
let floorCeilingArea = dimensions.width * dimensions.depth;
let frontBackArea = dimensions.depth * dimensions.height;
let totalArea = 2 * (leftRightArea + floorCeilingArea + frontBackArea);
for (let i = 0; i < Utils.NUMBER_REVERB_FREQUENCY_BANDS; i++) {
// Effective absorptive area.
let absorbtionArea = (coefficients.left[i] + coefficients.right[i]) * leftRightArea +
(coefficients.down[i] + coefficients.up[i]) * floorCeilingArea +
(coefficients.front[i] + coefficients.back[i]) * frontBackArea;
let meanAbsorbtionArea = absorbtionArea / totalArea;
// Compute reverberation using Eyring equation [1].
// [1] Beranek, Leo L. "Analysis of Sabine and Eyring equations and their
// application to concert hall audience and chair absorption." The
// Journal of the Acoustical Society of America, Vol. 120, No. 3.
// (2006), pp. 1399-1399.
durations[i] = Utils.ROOM_EYRING_CORRECTION_COEFFICIENT * k * volume /
(-totalArea * Math.log(1 - meanAbsorbtionArea) + 4 *
Utils.ROOM_AIR_ABSORPTION_COEFFICIENTS[i] * volume);
}
return durations;
}
/**
* Compute reflection coefficients from absorption coefficients.
* @param {Object} absorptionCoefficients
* @return {Object}
*/
function _computeReflectionCoefficients(absorptionCoefficients) {
let reflectionCoefficients = [];
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
// Compute average absorption coefficient (per wall).
reflectionCoefficients[property] = 0;
for (let j = 0; j < Utils.NUMBER_REFLECTION_AVERAGING_BANDS; j++) {
let bandIndex = j + Utils.ROOM_STARTING_AVERAGING_BAND;
reflectionCoefficients[property] +=
absorptionCoefficients[property][bandIndex];
}
reflectionCoefficients[property] /=
Utils.NUMBER_REFLECTION_AVERAGING_BANDS;
// Convert absorption coefficient to reflection coefficient.
reflectionCoefficients[property] =
Math.sqrt(1 - reflectionCoefficients[property]);
}
}
return reflectionCoefficients;
}
/**
* @class Room
* @description Model that manages early and late reflections using acoustic
* properties and listener position relative to a rectangular room.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Float32Array} options.listenerPosition
* The listener's initial position (in meters), where origin is the center of
* the room. Defaults to {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @param {Utils~RoomDimensions} options.dimensions Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Utils~RoomMaterials} options.materials Named acoustic materials per wall.
* Defaults to {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
* @param {Number} options.speedOfSound
* (in meters/second). Defaults to
* {@linkcode Utils.DEFAULT_SPEED_OF_SOUND DEFAULT_SPEED_OF_SOUND}.
*/
class Room {
constructor(context, options) {
// Public variables.
/**
* EarlyReflections {@link EarlyReflections EarlyReflections} submodule.
* @member {AudioNode} early
* @memberof Room
* @instance
*/
/**
* LateReflections {@link LateReflections LateReflections} submodule.
* @member {AudioNode} late
* @memberof Room
* @instance
*/
/**
* Ambisonic (multichannel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof Room
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.listenerPosition == undefined) {
options.listenerPosition = Utils.DEFAULT_POSITION.slice();
}
if (options.dimensions == undefined) {
options.dimensions = {};
Object.assign(options.dimensions, Utils.DEFAULT_ROOM_DIMENSIONS);
}
if (options.materials == undefined) {
options.materials = {};
Object.assign(options.materials, Utils.DEFAULT_ROOM_MATERIALS);
}
if (options.speedOfSound == undefined) {
options.speedOfSound = Utils.DEFAULT_SPEED_OF_SOUND;
}
// Sanitize room-properties-related arguments.
options.dimensions = _sanitizeDimensions(options.dimensions);
let absorptionCoefficients = _getCoefficientsFromMaterials(options.materials);
let reflectionCoefficients = _computeReflectionCoefficients(absorptionCoefficients);
let durations = _getDurationsFromProperties(options.dimensions, absorptionCoefficients, options.speedOfSound);
// Construct submodules for early and late reflections.
this.early = new EarlyReflections(context, {
dimensions: options.dimensions,
coefficients: reflectionCoefficients,
speedOfSound: options.speedOfSound,
listenerPosition: options.listenerPosition,
});
this.late = new LateReflections(context, {
durations: durations,
});
this.speedOfSound = options.speedOfSound;
// Construct auxillary audio nodes.
this.output = context.createGain();
this.early.output.connect(this.output);
this._merger = context.createChannelMerger(4);
this.late.output.connect(this._merger, 0, 0);
this._merger.connect(this.output);
}
/**
* Set the room's dimensions and wall materials.
* @param {Utils~RoomDimensions} dimensions Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Utils~RoomMaterials} materials Named acoustic materials per wall. Defaults to
* {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
*/
setProperties(dimensions, materials) {
// Compute late response.
let absorptionCoefficients = _getCoefficientsFromMaterials(materials);
let durations = _getDurationsFromProperties(dimensions, absorptionCoefficients, this.speedOfSound);
this.late.setDurations(durations);
// Compute early response.
this.early.speedOfSound = this.speedOfSound;
let reflectionCoefficients = _computeReflectionCoefficients(absorptionCoefficients);
this.early.setRoomProperties(dimensions, reflectionCoefficients);
}
/**
* Set the listener's position (in meters), where origin is the center of
* the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setListenerPosition(x, y, z) {
this.early.speedOfSound = this.speedOfSound;
this.early.setListenerPosition(x, y, z);
// Disable room effects if the listener is outside the room boundaries.
let distance = this.getDistanceOutsideRoom(x, y, z);
let gain = 1;
if (distance > Utils.EPSILON_FLOAT) {
gain = 1 - distance / Utils.LISTENER_MAX_OUTSIDE_ROOM_DISTANCE;
// Clamp gain between 0 and 1.
gain = Math.max(0, Math.min(1, gain));
}
this.output.gain.value = gain;
}
/**
* Compute distance outside room of provided position (in meters).
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @return {Number}
* Distance outside room (in meters). Returns 0 if inside room.
*/
getDistanceOutsideRoom(x, y, z) {
let dx = Math.max(0, -this.early._halfDimensions.width - x, x - this.early._halfDimensions.width);
let dy = Math.max(0, -this.early._halfDimensions.height - y, y - this.early._halfDimensions.height);
let dz = Math.max(0, -this.early._halfDimensions.depth - z, z - this.early._halfDimensions.depth);
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
}
export default Room;

View File

@@ -0,0 +1,182 @@
export default Source;
/**
* ~SourceOptions
*/
export type Source = {
/**
* The source's initial position (in meters), where origin is the center of
* the room. Defaults to {@link Utils.DEFAULT_POSITION DEFAULT_POSITION}.
*/
position: Float32Array;
/**
* The source's initial forward vector. Defaults to
* {@link Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
*/
forward: Float32Array;
/**
* The source's initial up vector. Defaults to
* {@link Utils.DEFAULT_UP DEFAULT_UP}.
*/
up: Float32Array;
/**
* Min. distance (in meters). Defaults to
* {@link Utils.DEFAULT_MIN_DISTANCE DEFAULT_MIN_DISTANCE}.
*/
minDistance: number;
/**
* Max. distance (in meters). Defaults to
* {@link Utils.DEFAULT_MAX_DISTANCE DEFAULT_MAX_DISTANCE}.
*/
maxDistance: number;
/**
* Rolloff model to use, chosen from options in
* {@link Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}. Defaults to
* {@link Utils.DEFAULT_ATTENUATION_ROLLOFF DEFAULT_ATTENUATION_ROLLOFF}.
*/
rolloff: string;
/**
* Input gain (linear). Defaults to
* {@link Utils.DEFAULT_SOURCE_GAIN DEFAULT_SOURCE_GAIN}.
*/
gain: number;
/**
* Directivity alpha. Defaults to
* {@link Utils.DEFAULT_DIRECTIVITY_ALPHA DEFAULT_DIRECTIVITY_ALPHA}.
*/
alpha: number;
/**
* Directivity sharpness. Defaults to
* {@link Utils.DEFAULT_DIRECTIVITY_SHARPNESS * DEFAULT_DIRECTIVITY_SHARPNESS}.
*/
sharpness: number;
/**
* Source width (in degrees). Where 0 degrees is a point source and 360 degrees
* is an omnidirectional source. Defaults to
* {@link Utils.DEFAULT_SOURCE_WIDTH DEFAULT_SOURCE_WIDTH}.
*/
sourceWidth: number;
};
/**
* Options for constructing a new Source.
* @typedef {Object} Source~SourceOptions
* @property {Float32Array} position
* The source's initial position (in meters), where origin is the center of
* the room. Defaults to {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @property {Float32Array} forward
* The source's initial forward vector. Defaults to
* {@linkcode Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
* @property {Float32Array} up
* The source's initial up vector. Defaults to
* {@linkcode Utils.DEFAULT_UP DEFAULT_UP}.
* @property {Number} minDistance
* Min. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MIN_DISTANCE DEFAULT_MIN_DISTANCE}.
* @property {Number} maxDistance
* Max. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MAX_DISTANCE DEFAULT_MAX_DISTANCE}.
* @property {string} rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}. Defaults to
* {@linkcode Utils.DEFAULT_ATTENUATION_ROLLOFF DEFAULT_ATTENUATION_ROLLOFF}.
* @property {Number} gain Input gain (linear). Defaults to
* {@linkcode Utils.DEFAULT_SOURCE_GAIN DEFAULT_SOURCE_GAIN}.
* @property {Number} alpha Directivity alpha. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_ALPHA DEFAULT_DIRECTIVITY_ALPHA}.
* @property {Number} sharpness Directivity sharpness. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_SHARPNESS
* DEFAULT_DIRECTIVITY_SHARPNESS}.
* @property {Number} sourceWidth
* Source width (in degrees). Where 0 degrees is a point source and 360 degrees
* is an omnidirectional source. Defaults to
* {@linkcode Utils.DEFAULT_SOURCE_WIDTH DEFAULT_SOURCE_WIDTH}.
*/
/**
* @class Source
* @description Source model to spatialize an audio buffer.
* @param {ResonanceAudio} scene Associated {@link ResonanceAudio
* ResonanceAudio} instance.
* @param {Source~SourceOptions} options
* Options for constructing a new Source.
*/
declare class Source {
constructor(scene: any, options: any);
_scene: any;
_position: any;
_forward: any;
_up: any;
_dx: Float32Array;
_right: Float32Array;
input: any;
_directivity: Directivity;
_toEarly: any;
_toLate: any;
_attenuation: Attenuation;
_encoder: Encoder;
/**
* Set source's position (in meters), where origin is the center of
* the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setPosition(x: number, y: number, z: number): void;
_update(): void;
/**
* Set source's rolloff.
* @param {string} rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}.
*/
setRolloff(rolloff: string): void;
/**
* Set source's minimum distance (in meters).
* @param {Number} minDistance
*/
setMinDistance(minDistance: number): void;
/**
* Set source's maximum distance (in meters).
* @param {Number} maxDistance
*/
setMaxDistance(maxDistance: number): void;
/**
* Set source's gain (linear).
* @param {Number} gain
*/
setGain(gain: number): void;
/**
* Set the source's orientation using forward and up vectors.
* @param {Number} forwardX
* @param {Number} forwardY
* @param {Number} forwardZ
* @param {Number} upX
* @param {Number} upY
* @param {Number} upZ
*/
setOrientation(forwardX: number, forwardY: number, forwardZ: number, upX: number, upY: number, upZ: number): void;
/**
* Set source's position and orientation using a
* Three.js modelViewMatrix object.
* @param {Float32Array} matrix4
* The Matrix4 representing the object position and rotation in world space.
*/
setFromMatrix(matrix4: Float32Array): void;
/**
* Set the source width (in degrees). Where 0 degrees is a point source and 360
* degrees is an omnidirectional source.
* @param {Number} sourceWidth (in degrees).
*/
setSourceWidth(sourceWidth: number): void;
/**
* Set source's directivity pattern (defined by alpha), where 0 is an
* omnidirectional pattern, 1 is a bidirectional pattern, 0.5 is a cardiod
* pattern. The sharpness of the pattern is increased exponentially.
* @param {Number} alpha
* Determines directivity pattern (0 to 1).
* @param {Number} sharpness
* Determines the sharpness of the directivity pattern (1 to Inf).
*/
setDirectivityPattern(alpha: number, sharpness: number): void;
}
import Directivity from "./directivity.js";
import Attenuation from "./attenuation.js";
import Encoder from "./encoder.js";

View File

@@ -0,0 +1,308 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file Source model to spatialize an audio buffer.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Directivity from './directivity.js';
import Attenuation from './attenuation.js';
import Encoder from './encoder.js';
import Utils from './utils.js';
/**
* Options for constructing a new Source.
* @typedef {Object} Source~SourceOptions
* @property {Float32Array} position
* The source's initial position (in meters), where origin is the center of
* the room. Defaults to {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
* @property {Float32Array} forward
* The source's initial forward vector. Defaults to
* {@linkcode Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
* @property {Float32Array} up
* The source's initial up vector. Defaults to
* {@linkcode Utils.DEFAULT_UP DEFAULT_UP}.
* @property {Number} minDistance
* Min. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MIN_DISTANCE DEFAULT_MIN_DISTANCE}.
* @property {Number} maxDistance
* Max. distance (in meters). Defaults to
* {@linkcode Utils.DEFAULT_MAX_DISTANCE DEFAULT_MAX_DISTANCE}.
* @property {string} rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}. Defaults to
* {@linkcode Utils.DEFAULT_ATTENUATION_ROLLOFF DEFAULT_ATTENUATION_ROLLOFF}.
* @property {Number} gain Input gain (linear). Defaults to
* {@linkcode Utils.DEFAULT_SOURCE_GAIN DEFAULT_SOURCE_GAIN}.
* @property {Number} alpha Directivity alpha. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_ALPHA DEFAULT_DIRECTIVITY_ALPHA}.
* @property {Number} sharpness Directivity sharpness. Defaults to
* {@linkcode Utils.DEFAULT_DIRECTIVITY_SHARPNESS
* DEFAULT_DIRECTIVITY_SHARPNESS}.
* @property {Number} sourceWidth
* Source width (in degrees). Where 0 degrees is a point source and 360 degrees
* is an omnidirectional source. Defaults to
* {@linkcode Utils.DEFAULT_SOURCE_WIDTH DEFAULT_SOURCE_WIDTH}.
*/
/**
* @class Source
* @description Source model to spatialize an audio buffer.
* @param {ResonanceAudio} scene Associated {@link ResonanceAudio
* ResonanceAudio} instance.
* @param {Source~SourceOptions} options
* Options for constructing a new Source.
*/
class Source {
constructor(scene, options) {
// Public variables.
/**
* Mono (1-channel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof Source
* @instance
*/
/**
*
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.position == undefined) {
options.position = Utils.DEFAULT_POSITION.slice();
}
if (options.forward == undefined) {
options.forward = Utils.DEFAULT_FORWARD.slice();
}
if (options.up == undefined) {
options.up = Utils.DEFAULT_UP.slice();
}
if (options.minDistance == undefined) {
options.minDistance = Utils.DEFAULT_MIN_DISTANCE;
}
if (options.maxDistance == undefined) {
options.maxDistance = Utils.DEFAULT_MAX_DISTANCE;
}
if (options.rolloff == undefined) {
options.rolloff = Utils.DEFAULT_ROLLOFF;
}
if (options.gain == undefined) {
options.gain = Utils.DEFAULT_SOURCE_GAIN;
}
if (options.alpha == undefined) {
options.alpha = Utils.DEFAULT_DIRECTIVITY_ALPHA;
}
if (options.sharpness == undefined) {
options.sharpness = Utils.DEFAULT_DIRECTIVITY_SHARPNESS;
}
if (options.sourceWidth == undefined) {
options.sourceWidth = Utils.DEFAULT_SOURCE_WIDTH;
}
// Member variables.
this._scene = scene;
this._position = options.position;
this._forward = options.forward;
this._up = options.up;
this._dx = new Float32Array(3);
this._right = Utils.crossProduct(this._forward, this._up);
// Create audio nodes.
let context = scene._context;
this.input = context.createGain();
this._directivity = new Directivity(context, {
alpha: options.alpha,
sharpness: options.sharpness,
});
this._toEarly = context.createGain();
this._toLate = context.createGain();
this._attenuation = new Attenuation(context, {
minDistance: options.minDistance,
maxDistance: options.maxDistance,
rolloff: options.rolloff,
});
this._encoder = new Encoder(context, {
ambisonicOrder: scene._ambisonicOrder,
sourceWidth: options.sourceWidth,
});
// Connect nodes.
this.input.connect(this._toLate);
this._toLate.connect(scene._room.late.input);
this.input.connect(this._attenuation.input);
this._attenuation.output.connect(this._toEarly);
this._toEarly.connect(scene._room.early.input);
this._attenuation.output.connect(this._directivity.input);
this._directivity.output.connect(this._encoder.input);
this._encoder.output.connect(scene._listener.input);
// Assign initial conditions.
this.setPosition(options.position[0], options.position[1], options.position[2]);
this.input.gain.value = options.gain;
}
/**
* Set source's position (in meters), where origin is the center of
* the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setPosition(x, y, z) {
// Assign new position.
this._position[0] = x;
this._position[1] = y;
this._position[2] = z;
// Handle far-field effect.
let distance = this._scene._room.getDistanceOutsideRoom(this._position[0], this._position[1], this._position[2]);
let gain = _computeDistanceOutsideRoom(distance);
this._toLate.gain.value = gain;
this._toEarly.gain.value = gain;
this._update();
}
// Update the source when changing the listener's position.
_update() {
// Compute distance to listener.
for (let i = 0; i < 3; i++) {
this._dx[i] = this._position[i] - this._scene._listener.position[i];
}
let distance = Math.sqrt(this._dx[0] * this._dx[0] +
this._dx[1] * this._dx[1] + this._dx[2] * this._dx[2]);
if (distance > 0) {
// Normalize direction vector.
this._dx[0] /= distance;
this._dx[1] /= distance;
this._dx[2] /= distance;
}
// Compuete angle of direction vector.
let azimuth = Math.atan2(-this._dx[0], this._dx[2]) *
Utils.RADIANS_TO_DEGREES;
let elevation = Math.atan2(this._dx[1], Math.sqrt(this._dx[0] * this._dx[0] +
this._dx[2] * this._dx[2])) * Utils.RADIANS_TO_DEGREES;
// Set distance/directivity/direction values.
this._attenuation.setDistance(distance);
this._directivity.computeAngle(this._forward, this._dx);
this._encoder.setDirection(azimuth, elevation);
}
/**
* Set source's rolloff.
* @param {string} rolloff
* Rolloff model to use, chosen from options in
* {@linkcode Utils.ATTENUATION_ROLLOFFS ATTENUATION_ROLLOFFS}.
*/
setRolloff(rolloff) {
this._attenuation.setRolloff(rolloff);
}
/**
* Set source's minimum distance (in meters).
* @param {Number} minDistance
*/
setMinDistance(minDistance) {
this._attenuation.minDistance = minDistance;
}
/**
* Set source's maximum distance (in meters).
* @param {Number} maxDistance
*/
setMaxDistance(maxDistance) {
this._attenuation.maxDistance = maxDistance;
}
/**
* Set source's gain (linear).
* @param {Number} gain
*/
setGain(gain) {
this.input.gain.value = gain;
}
/**
* Set the source's orientation using forward and up vectors.
* @param {Number} forwardX
* @param {Number} forwardY
* @param {Number} forwardZ
* @param {Number} upX
* @param {Number} upY
* @param {Number} upZ
*/
setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ) {
this._forward[0] = forwardX;
this._forward[1] = forwardY;
this._forward[2] = forwardZ;
this._up[0] = upX;
this._up[1] = upY;
this._up[2] = upZ;
this._right = Utils.crossProduct(this._forward, this._up);
}
// TODO(bitllama): Make sure this works with Three.js as intended.
/**
* Set source's position and orientation using a
* Three.js modelViewMatrix object.
* @param {Float32Array} matrix4
* The Matrix4 representing the object position and rotation in world space.
*/
setFromMatrix(matrix4) {
this._right[0] = matrix4.elements[0];
this._right[1] = matrix4.elements[1];
this._right[2] = matrix4.elements[2];
this._up[0] = matrix4.elements[4];
this._up[1] = matrix4.elements[5];
this._up[2] = matrix4.elements[6];
this._forward[0] = matrix4.elements[8];
this._forward[1] = matrix4.elements[9];
this._forward[2] = matrix4.elements[10];
// Normalize to remove scaling.
this._right = Utils.normalizeVector(this._right);
this._up = Utils.normalizeVector(this._up);
this._forward = Utils.normalizeVector(this._forward);
// Update position.
this.setPosition(matrix4.elements[12], matrix4.elements[13], matrix4.elements[14]);
}
/**
* Set the source width (in degrees). Where 0 degrees is a point source and 360
* degrees is an omnidirectional source.
* @param {Number} sourceWidth (in degrees).
*/
setSourceWidth(sourceWidth) {
this._encoder.setSourceWidth(sourceWidth);
this.setPosition(this._position[0], this._position[1], this._position[2]);
}
/**
* Set source's directivity pattern (defined by alpha), where 0 is an
* omnidirectional pattern, 1 is a bidirectional pattern, 0.5 is a cardiod
* pattern. The sharpness of the pattern is increased exponentially.
* @param {Number} alpha
* Determines directivity pattern (0 to 1).
* @param {Number} sharpness
* Determines the sharpness of the directivity pattern (1 to Inf).
*/
setDirectivityPattern(alpha, sharpness) {
this._directivity.setPattern(alpha, sharpness);
this.setPosition(this._position[0], this._position[1], this._position[2]);
}
}
/**
* Determine the distance a source is outside of a room. Attenuate gain going
* to the reflections and reverb when the source is outside of the room.
* @param {Number} distance Distance in meters.
* @return {Number} Gain (linear) of source.
* @private
*/
function _computeDistanceOutsideRoom(distance) {
// We apply a linear ramp from 1 to 0 as the source is up to 1m outside.
let gain = 1;
if (distance > Utils.EPSILON_FLOAT) {
gain = 1 - distance / Utils.SOURCE_MAX_OUTSIDE_ROOM_DISTANCE;
// Clamp gain between 0 and 1.
gain = Math.max(0, Math.min(1, gain));
}
return gain;
}
export default Source;

View File

@@ -0,0 +1,38 @@
declare namespace _default {
export { SPHERICAL_HARMONICS };
export { SPHERICAL_HARMONICS_AZIMUTH_RESOLUTION };
export { SPHERICAL_HARMONICS_ELEVATION_RESOLUTION };
export { SPHERICAL_HARMONICS_MAX_ORDER };
export { MAX_RE_WEIGHTS };
export { MAX_RE_WEIGHTS_RESOLUTION };
}
export default _default;
/**
* Pre-computed Spherical Harmonics Coefficients.
*
* This function generates an efficient lookup table of SH coefficients. It
* exploits the way SHs are generated (i.e. Ylm = Nlm * Plm * Em). Since Nlm
* & Plm coefficients only depend on theta, and Em only depends on phi, we
* can separate the equation along these lines. Em does not depend on
* degree, so we only need to compute (2 * l) per azimuth Em total and
* Nlm * Plm is symmetrical across indexes, so only positive indexes are
* computed ((l + 1) * (l + 2) / 2 - 1) per elevation.
* @type {Float32Array}
*/
declare const SPHERICAL_HARMONICS: Float32Array;
/** @type {Number} */
declare const SPHERICAL_HARMONICS_AZIMUTH_RESOLUTION: number;
/** @type {Number} */
declare const SPHERICAL_HARMONICS_ELEVATION_RESOLUTION: number;
/**
* The maximum allowed ambisonic order.
* @type {Number}
*/
declare const SPHERICAL_HARMONICS_MAX_ORDER: number;
/**
* Pre-computed per-band weighting coefficients for producing energy-preserving
* Max-Re sources.
*/
declare const MAX_RE_WEIGHTS: number[][];
/** @type {Number} */
declare const MAX_RE_WEIGHTS_RESOLUTION: number;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
export default Utils;
/**
* @class Utils
* @description A set of defaults, constants and utility functions.
*/
declare class Utils {
/**
* Properties describing the geometry of a room.
* @typedef {Object} Utils~RoomDimensions
* @property {Number} width (in meters).
* @property {Number} height (in meters).
* @property {Number} depth (in meters).
*/
/**
* Properties describing the wall materials (from
* {@linkcode Utils.ROOM_MATERIAL_COEFFICIENTS ROOM_MATERIAL_COEFFICIENTS})
* of a room.
* @typedef {Object} Utils~RoomMaterials
* @property {String} left Left-wall material name.
* @property {String} right Right-wall material name.
* @property {String} front Front-wall material name.
* @property {String} back Back-wall material name.
* @property {String} up Up-wall material name.
* @property {String} down Down-wall material name.
*/
/**
* ResonanceAudio library logging function.
* @type {Function}
* @param {any} Message to be printed out.
* @private
*/
private static log;
}
declare namespace Utils {
const DEFAULT_SOURCE_GAIN: number;
const LISTENER_MAX_OUTSIDE_ROOM_DISTANCE: number;
const SOURCE_MAX_OUTSIDE_ROOM_DISTANCE: number;
const DEFAULT_SOURCE_DISTANCE: number;
const DEFAULT_POSITION: Float32Array;
const DEFAULT_FORWARD: Float32Array;
const DEFAULT_UP: Float32Array;
const DEFAULT_RIGHT: Float32Array;
const DEFAULT_SPEED_OF_SOUND: number;
const ATTENUATION_ROLLOFFS: any[];
const DEFAULT_ATTENUATION_ROLLOFF: string;
const DEFAULT_MIN_DISTANCE: number;
const DEFAULT_MAX_DISTANCE: number;
const DEFAULT_DIRECTIVITY_ALPHA: number;
const DEFAULT_DIRECTIVITY_SHARPNESS: number;
const DEFAULT_AZIMUTH: number;
const DEFAULT_ELEVATION: number;
const DEFAULT_AMBISONIC_ORDER: number;
const DEFAULT_SOURCE_WIDTH: number;
const DEFAULT_REFLECTION_MAX_DURATION: number;
const DEFAULT_REFLECTION_CUTOFF_FREQUENCY: number;
const DEFAULT_REFLECTION_COEFFICIENTS: any;
const DEFAULT_REFLECTION_MIN_DISTANCE: number;
const DEFAULT_ROOM_DIMENSIONS: any;
const DEFAULT_REFLECTION_MULTIPLIER: number;
const DEFAULT_REVERB_BANDWIDTH: number;
const DEFAULT_REVERB_DURATION_MULTIPLIER: number;
const DEFAULT_REVERB_PREDELAY: number;
const DEFAULT_REVERB_TAIL_ONSET: number;
const DEFAULT_REVERB_GAIN: number;
const DEFAULT_REVERB_MAX_DURATION: number;
const DEFAULT_REVERB_FREQUENCY_BANDS: any[];
const NUMBER_REVERB_FREQUENCY_BANDS: number;
const DEFAULT_REVERB_DURATIONS: Float32Array;
const ROOM_MATERIAL_COEFFICIENTS: any;
const DEFAULT_ROOM_MATERIALS: any;
const NUMBER_REFLECTION_AVERAGING_BANDS: number;
const ROOM_STARTING_AVERAGING_BAND: number;
const ROOM_MIN_VOLUME: number;
const ROOM_AIR_ABSORPTION_COEFFICIENTS: Float32Array;
const ROOM_EYRING_CORRECTION_COEFFICIENT: number;
const TWO_PI: number;
const TWENTY_FOUR_LOG10: number;
const LOG1000: number;
const LOG2_DIV2: number;
const DEGREES_TO_RADIANS: number;
const RADIANS_TO_DEGREES: number;
const EPSILON_FLOAT: number;
/**
* Normalize a 3-d vector.
* @param {Float32Array} v 3-element vector.
* @return {Float32Array} 3-element vector.
* @private
*/
function normalizeVector(v: Float32Array): Float32Array;
/**
* Cross-product between two 3-d vectors.
* @param {Float32Array} a 3-element vector.
* @param {Float32Array} b 3-element vector.
* @return {Float32Array}
* @private
*/
function crossProduct(a: Float32Array, b: Float32Array): Float32Array;
}

View File

@@ -0,0 +1,379 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file ResonanceAudio library common utilities, mathematical constants,
* and default values.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
/**
* @class Utils
* @description A set of defaults, constants and utility functions.
*/
class Utils {
/**
* Properties describing the geometry of a room.
* @typedef {Object} Utils~RoomDimensions
* @property {Number} width (in meters).
* @property {Number} height (in meters).
* @property {Number} depth (in meters).
*/
/**
* Properties describing the wall materials (from
* {@linkcode Utils.ROOM_MATERIAL_COEFFICIENTS ROOM_MATERIAL_COEFFICIENTS})
* of a room.
* @typedef {Object} Utils~RoomMaterials
* @property {String} left Left-wall material name.
* @property {String} right Right-wall material name.
* @property {String} front Front-wall material name.
* @property {String} back Back-wall material name.
* @property {String} up Up-wall material name.
* @property {String} down Down-wall material name.
*/
/**
* ResonanceAudio library logging function.
* @type {Function}
* @param {any} Message to be printed out.
* @private
*/
static log() {
window.console.log.apply(window.console, [
'%c[ResonanceAudio]%c '
+ Array.prototype.slice.call(arguments).join(' ') + ' %c(@'
+ performance.now().toFixed(2) + 'ms)',
'background: #BBDEFB; color: #FF5722; font-weight: 700',
'font-weight: 400',
'color: #AAA',
]);
}
}
/**
* Default input gain (linear).
* @type {Number}
*/
Utils.DEFAULT_SOURCE_GAIN = 1;
/**
* Maximum outside-the-room distance to attenuate far-field listener by.
* @type {Number}
*/
Utils.LISTENER_MAX_OUTSIDE_ROOM_DISTANCE = 1;
/**
* Maximum outside-the-room distance to attenuate far-field sources by.
* @type {Number}
*/
Utils.SOURCE_MAX_OUTSIDE_ROOM_DISTANCE = 1;
/**
* Default distance from listener when setting angle.
* @type {Number}
*/
Utils.DEFAULT_SOURCE_DISTANCE = 1;
/** @type {Float32Array} */
Utils.DEFAULT_POSITION = [0, 0, 0];
/** @type {Float32Array} */
Utils.DEFAULT_FORWARD = [0, 0, -1];
/** @type {Float32Array} */
Utils.DEFAULT_UP = [0, 1, 0];
/** @type {Float32Array} */
Utils.DEFAULT_RIGHT = [1, 0, 0];
/**
* @type {Number}
*/
Utils.DEFAULT_SPEED_OF_SOUND = 343;
/** Rolloff models (e.g. 'logarithmic', 'linear', or 'none').
* @type {Array}
*/
Utils.ATTENUATION_ROLLOFFS = ['logarithmic', 'linear', 'none'];
/** Default rolloff model ('logarithmic').
* @type {string}
*/
Utils.DEFAULT_ATTENUATION_ROLLOFF = 'logarithmic';
/** @type {Number} */
Utils.DEFAULT_MIN_DISTANCE = 1;
/** @type {Number} */
Utils.DEFAULT_MAX_DISTANCE = 1000;
/**
* The default alpha (i.e. microphone pattern).
* @type {Number}
*/
Utils.DEFAULT_DIRECTIVITY_ALPHA = 0;
/**
* The default pattern sharpness (i.e. pattern exponent).
* @type {Number}
*/
Utils.DEFAULT_DIRECTIVITY_SHARPNESS = 1;
/**
* Default azimuth (in degrees). Suitable range is 0 to 360.
* @type {Number}
*/
Utils.DEFAULT_AZIMUTH = 0;
/**
* Default elevation (in degres).
* Suitable range is from -90 (below) to 90 (above).
* @type {Number}
*/
Utils.DEFAULT_ELEVATION = 0;
/**
* The default ambisonic order.
* @type {Number}
*/
Utils.DEFAULT_AMBISONIC_ORDER = 1;
/**
* The default source width.
* @type {Number}
*/
Utils.DEFAULT_SOURCE_WIDTH = 0;
/**
* The maximum delay (in seconds) of a single wall reflection.
* @type {Number}
*/
Utils.DEFAULT_REFLECTION_MAX_DURATION = 0.5;
/**
* The -12dB cutoff frequency (in Hertz) for the lowpass filter applied to
* all reflections.
* @type {Number}
*/
Utils.DEFAULT_REFLECTION_CUTOFF_FREQUENCY = 6400; // Uses -12dB cutoff.
/**
* The default reflection coefficients (where 0 = no reflection, 1 = perfect
* reflection, -1 = mirrored reflection (180-degrees out of phase)).
* @type {Object}
*/
Utils.DEFAULT_REFLECTION_COEFFICIENTS = {
left: 0, right: 0, front: 0, back: 0, down: 0, up: 0,
};
/**
* The minimum distance we consider the listener to be to any given wall.
* @type {Number}
*/
Utils.DEFAULT_REFLECTION_MIN_DISTANCE = 1;
/**
* Default room dimensions (in meters).
* @type {Object}
*/
Utils.DEFAULT_ROOM_DIMENSIONS = {
width: 0, height: 0, depth: 0,
};
/**
* The multiplier to apply to distances from the listener to each wall.
* @type {Number}
*/
Utils.DEFAULT_REFLECTION_MULTIPLIER = 1;
/** The default bandwidth (in octaves) of the center frequencies.
* @type {Number}
*/
Utils.DEFAULT_REVERB_BANDWIDTH = 1;
/** The default multiplier applied when computing tail lengths.
* @type {Number}
*/
Utils.DEFAULT_REVERB_DURATION_MULTIPLIER = 1;
/**
* The late reflections pre-delay (in milliseconds).
* @type {Number}
*/
Utils.DEFAULT_REVERB_PREDELAY = 1.5;
/**
* The length of the beginning of the impulse response to apply a
* half-Hann window to.
* @type {Number}
*/
Utils.DEFAULT_REVERB_TAIL_ONSET = 3.8;
/**
* The default gain (linear).
* @type {Number}
*/
Utils.DEFAULT_REVERB_GAIN = 0.01;
/**
* The maximum impulse response length (in seconds).
* @type {Number}
*/
Utils.DEFAULT_REVERB_MAX_DURATION = 3;
/**
* Center frequencies of the multiband late reflections.
* Nine bands are computed by: 31.25 * 2^(0:8).
* @type {Array}
*/
Utils.DEFAULT_REVERB_FREQUENCY_BANDS = [
31.25, 62.5, 125, 250, 500, 1000, 2000, 4000, 8000,
];
/**
* The number of frequency bands.
*/
Utils.NUMBER_REVERB_FREQUENCY_BANDS =
Utils.DEFAULT_REVERB_FREQUENCY_BANDS.length;
/**
* The default multiband RT60 durations (in seconds).
* @type {Float32Array}
*/
Utils.DEFAULT_REVERB_DURATIONS =
new Float32Array(Utils.NUMBER_REVERB_FREQUENCY_BANDS);
/**
* Pre-defined frequency-dependent absorption coefficients for listed materials.
* Currently supported materials are:
* <ul>
* <li>'transparent'</li>
* <li>'acoustic-ceiling-tiles'</li>
* <li>'brick-bare'</li>
* <li>'brick-painted'</li>
* <li>'concrete-block-coarse'</li>
* <li>'concrete-block-painted'</li>
* <li>'curtain-heavy'</li>
* <li>'fiber-glass-insulation'</li>
* <li>'glass-thin'</li>
* <li>'glass-thick'</li>
* <li>'grass'</li>
* <li>'linoleum-on-concrete'</li>
* <li>'marble'</li>
* <li>'metal'</li>
* <li>'parquet-on-concrete'</li>
* <li>'plaster-smooth'</li>
* <li>'plywood-panel'</li>
* <li>'polished-concrete-or-tile'</li>
* <li>'sheetrock'</li>
* <li>'water-or-ice-surface'</li>
* <li>'wood-ceiling'</li>
* <li>'wood-panel'</li>
* <li>'uniform'</li>
* </ul>
* @type {Object}
*/
Utils.ROOM_MATERIAL_COEFFICIENTS = {
'transparent': [1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000],
'acoustic-ceiling-tiles': [0.672, 0.675, 0.700, 0.660, 0.720, 0.920, 0.880, 0.750, 1.000],
'brick-bare': [0.030, 0.030, 0.030, 0.030, 0.030, 0.040, 0.050, 0.070, 0.140],
'brick-painted': [0.006, 0.007, 0.010, 0.010, 0.020, 0.020, 0.020, 0.030, 0.060],
'concrete-block-coarse': [0.360, 0.360, 0.360, 0.440, 0.310, 0.290, 0.390, 0.250, 0.500],
'concrete-block-painted': [0.092, 0.090, 0.100, 0.050, 0.060, 0.070, 0.090, 0.080, 0.160],
'curtain-heavy': [0.073, 0.106, 0.140, 0.350, 0.550, 0.720, 0.700, 0.650, 1.000],
'fiber-glass-insulation': [0.193, 0.220, 0.220, 0.820, 0.990, 0.990, 0.990, 0.990, 1.000],
'glass-thin': [0.180, 0.169, 0.180, 0.060, 0.040, 0.030, 0.020, 0.020, 0.040],
'glass-thick': [0.350, 0.350, 0.350, 0.250, 0.180, 0.120, 0.070, 0.040, 0.080],
'grass': [0.050, 0.050, 0.150, 0.250, 0.400, 0.550, 0.600, 0.600, 0.600],
'linoleum-on-concrete': [0.020, 0.020, 0.020, 0.030, 0.030, 0.030, 0.030, 0.020, 0.040],
'marble': [0.010, 0.010, 0.010, 0.010, 0.010, 0.010, 0.020, 0.020, 0.040],
'metal': [0.030, 0.035, 0.040, 0.040, 0.050, 0.050, 0.050, 0.070, 0.090],
'parquet-on-concrete': [0.028, 0.030, 0.040, 0.040, 0.070, 0.060, 0.060, 0.070, 0.140],
'plaster-rough': [0.017, 0.018, 0.020, 0.030, 0.040, 0.050, 0.040, 0.030, 0.060],
'plaster-smooth': [0.011, 0.012, 0.013, 0.015, 0.020, 0.030, 0.040, 0.050, 0.100],
'plywood-panel': [0.400, 0.340, 0.280, 0.220, 0.170, 0.090, 0.100, 0.110, 0.220],
'polished-concrete-or-tile': [0.008, 0.008, 0.010, 0.010, 0.015, 0.020, 0.020, 0.020, 0.040],
'sheet-rock': [0.290, 0.279, 0.290, 0.100, 0.050, 0.040, 0.070, 0.090, 0.180],
'water-or-ice-surface': [0.006, 0.006, 0.008, 0.008, 0.013, 0.015, 0.020, 0.025, 0.050],
'wood-ceiling': [0.150, 0.147, 0.150, 0.110, 0.100, 0.070, 0.060, 0.070, 0.140],
'wood-panel': [0.280, 0.280, 0.280, 0.220, 0.170, 0.090, 0.100, 0.110, 0.220],
'uniform': [0.500, 0.500, 0.500, 0.500, 0.500, 0.500, 0.500, 0.500, 0.500],
};
/**
* Default materials that use strings from
* {@linkcode Utils.MATERIAL_COEFFICIENTS MATERIAL_COEFFICIENTS}
* @type {Object}
*/
Utils.DEFAULT_ROOM_MATERIALS = {
left: 'transparent', right: 'transparent', front: 'transparent',
back: 'transparent', down: 'transparent', up: 'transparent',
};
/**
* The number of bands to average over when computing reflection coefficients.
* @type {Number}
*/
Utils.NUMBER_REFLECTION_AVERAGING_BANDS = 3;
/**
* The starting band to average over when computing reflection coefficients.
* @type {Number}
*/
Utils.ROOM_STARTING_AVERAGING_BAND = 4;
/**
* The minimum threshold for room volume.
* Room model is disabled if volume is below this value.
* @type {Number} */
Utils.ROOM_MIN_VOLUME = 1e-4;
/**
* Air absorption coefficients per frequency band.
* @type {Float32Array}
*/
Utils.ROOM_AIR_ABSORPTION_COEFFICIENTS =
[0.0006, 0.0006, 0.0007, 0.0008, 0.0010, 0.0015, 0.0026, 0.0060, 0.0207];
/**
* A scalar correction value to ensure Sabine and Eyring produce the same RT60
* value at the cross-over threshold.
* @type {Number}
*/
Utils.ROOM_EYRING_CORRECTION_COEFFICIENT = 1.38;
/**
* @type {Number}
* @private
*/
Utils.TWO_PI = 6.28318530717959;
/**
* @type {Number}
* @private
*/
Utils.TWENTY_FOUR_LOG10 = 55.2620422318571;
/**
* @type {Number}
* @private
*/
Utils.LOG1000 = 6.90775527898214;
/**
* @type {Number}
* @private
*/
Utils.LOG2_DIV2 = 0.346573590279973;
/**
* @type {Number}
* @private
*/
Utils.DEGREES_TO_RADIANS = 0.017453292519943;
/**
* @type {Number}
* @private
*/
Utils.RADIANS_TO_DEGREES = 57.295779513082323;
/**
* @type {Number}
* @private
*/
Utils.EPSILON_FLOAT = 1e-8;
/**
* Normalize a 3-d vector.
* @param {Float32Array} v 3-element vector.
* @return {Float32Array} 3-element vector.
* @private
*/
Utils.normalizeVector = v => {
let n = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (n > Utils.EPSILON_FLOAT) {
n = 1 / n;
v[0] *= n;
v[1] *= n;
v[2] *= n;
}
return v;
};
/**
* Cross-product between two 3-d vectors.
* @param {Float32Array} a 3-element vector.
* @param {Float32Array} b 3-element vector.
* @return {Float32Array}
* @private
*/
Utils.crossProduct = (a, b) => {
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
];
};
export default Utils;

View File

@@ -0,0 +1,2 @@
declare var _default: "1.0.0";
export default _default;

View File

@@ -0,0 +1,24 @@
/**
* Copyright 2017 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file ResonanceAudio version.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
/**
* ResonanceAudio library version
* @type {String}
*/
export default '1.0.0';

View File

@@ -0,0 +1 @@
export declare const epsilon = 0.00001;

View File

@@ -0,0 +1 @@
export const epsilon = 0.00001;

View File

@@ -0,0 +1,23 @@
import vec2 from './vec2';
export default class mat2 {
constructor(values?: number[]);
private values;
static readonly identity: mat2;
at(index: number): number;
init(values: number[]): mat2;
reset(): void;
copy(dest?: mat2): mat2;
all(): number[];
row(index: number): number[];
col(index: number): number[];
equals(matrix: mat2, threshold?: number): boolean;
determinant(): number;
setIdentity(): mat2;
transpose(): mat2;
inverse(): mat2;
multiply(matrix: mat2): mat2;
rotate(angle: number): mat2;
multiplyVec2(vector: vec2, result: vec2): vec2;
scale(vector: vec2): mat2;
static product(m1: mat2, m2: mat2, result: mat2): mat2;
}

161
framework/resonator/vendor/tsm/mat2.js vendored Normal file
View File

@@ -0,0 +1,161 @@
import vec2 from './vec2';
import { epsilon } from './constants';
export default class mat2 {
constructor(values) {
this.values = new Float32Array(4);
if (values !== undefined) {
this.init(values);
}
}
at(index) {
return this.values[index];
}
init(values) {
for (let i = 0; i < 4; i++) {
this.values[i] = values[i];
}
return this;
}
reset() {
for (let i = 0; i < 4; i++) {
this.values[i] = 0;
}
}
copy(dest) {
if (!dest) {
dest = new mat2();
}
for (let i = 0; i < 4; i++) {
dest.values[i] = this.values[i];
}
return dest;
}
all() {
const data = [];
for (let i = 0; i < 4; i++) {
data[i] = this.values[i];
}
return data;
}
row(index) {
return [this.values[index * 2 + 0], this.values[index * 2 + 1]];
}
col(index) {
return [this.values[index], this.values[index + 2]];
}
equals(matrix, threshold = epsilon) {
for (let i = 0; i < 4; i++) {
if (Math.abs(this.values[i] - matrix.at(i)) > threshold) {
return false;
}
}
return true;
}
determinant() {
return this.values[0] * this.values[3] - this.values[2] * this.values[1];
}
setIdentity() {
this.values[0] = 1;
this.values[1] = 0;
this.values[2] = 0;
this.values[3] = 1;
return this;
}
transpose() {
const temp = this.values[1];
this.values[1] = this.values[2];
this.values[2] = temp;
return this;
}
inverse() {
let det = this.determinant();
if (!det) {
return null;
}
det = 1.0 / det;
const a11 = this.values[0];
this.values[0] = det * this.values[3];
this.values[1] = det * -this.values[1];
this.values[2] = det * -this.values[2];
this.values[3] = det * a11;
return this;
}
multiply(matrix) {
const a11 = this.values[0];
const a12 = this.values[1];
const a21 = this.values[2];
const a22 = this.values[3];
this.values[0] = a11 * matrix.at(0) + a12 * matrix.at(2);
this.values[1] = a11 * matrix.at(1) + a12 * matrix.at(3);
this.values[2] = a21 * matrix.at(0) + a22 * matrix.at(2);
this.values[3] = a21 * matrix.at(1) + a22 * matrix.at(3);
return this;
}
rotate(angle) {
const a11 = this.values[0];
const a12 = this.values[1];
const a21 = this.values[2];
const a22 = this.values[3];
const sin = Math.sin(angle);
const cos = Math.cos(angle);
this.values[0] = a11 * cos + a12 * sin;
this.values[1] = a11 * -sin + a12 * cos;
this.values[2] = a21 * cos + a22 * sin;
this.values[3] = a21 * -sin + a22 * cos;
return this;
}
multiplyVec2(vector, result) {
const x = vector.x;
const y = vector.y;
if (result) {
result.xy = [
x * this.values[0] + y * this.values[1],
x * this.values[2] + y * this.values[3]
];
return result;
}
else {
return new vec2([
x * this.values[0] + y * this.values[1],
x * this.values[2] + y * this.values[3]
]);
}
}
scale(vector) {
const a11 = this.values[0];
const a12 = this.values[1];
const a21 = this.values[2];
const a22 = this.values[3];
const x = vector.x;
const y = vector.y;
this.values[0] = a11 * x;
this.values[1] = a12 * y;
this.values[2] = a21 * x;
this.values[3] = a22 * y;
return this;
}
static product(m1, m2, result) {
const a11 = m1.at(0);
const a12 = m1.at(1);
const a21 = m1.at(2);
const a22 = m1.at(3);
if (result) {
result.init([
a11 * m2.at(0) + a12 * m2.at(2),
a11 * m2.at(1) + a12 * m2.at(3),
a21 * m2.at(0) + a22 * m2.at(2),
a21 * m2.at(1) + a22 * m2.at(3)
]);
return result;
}
else {
return new mat2([
a11 * m2.at(0) + a12 * m2.at(2),
a11 * m2.at(1) + a12 * m2.at(3),
a21 * m2.at(0) + a22 * m2.at(2),
a21 * m2.at(1) + a22 * m2.at(3)
]);
}
}
}
mat2.identity = new mat2().setIdentity();

View File

@@ -0,0 +1,28 @@
import mat4 from './mat4';
import quat from './quat';
import vec2 from './vec2';
import vec3 from './vec3';
export default class mat3 {
constructor(values?: number[]);
private values;
static readonly identity: mat3;
at(index: number): number;
init(values: number[]): mat3;
reset(): void;
copy(dest?: mat3): mat3;
all(): number[];
row(index: number): number[];
col(index: number): number[];
equals(matrix: mat3, threshold?: number): boolean;
determinant(): number;
setIdentity(): mat3;
transpose(): mat3;
inverse(): mat3;
multiply(matrix: mat3): mat3;
multiplyVec2(vector: vec2, result: vec2): vec2;
multiplyVec3(vector: vec3, result: vec3): vec3;
toMat4(result: mat4): mat4;
toQuat(): quat;
rotate(angle: number, axis: vec3): mat3;
static product(m1: mat3, m2: mat3, result: mat3): mat3;
}

392
framework/resonator/vendor/tsm/mat3.js vendored Normal file
View File

@@ -0,0 +1,392 @@
import mat4 from './mat4';
import quat from './quat';
import vec2 from './vec2';
import vec3 from './vec3';
import { epsilon } from './constants';
export default class mat3 {
constructor(values) {
this.values = new Float32Array(9);
if (values !== undefined) {
this.init(values);
}
}
at(index) {
return this.values[index];
}
init(values) {
for (let i = 0; i < 9; i++) {
this.values[i] = values[i];
}
return this;
}
reset() {
for (let i = 0; i < 9; i++) {
this.values[i] = 0;
}
}
copy(dest) {
if (!dest) {
dest = new mat3();
}
for (let i = 0; i < 9; i++) {
dest.values[i] = this.values[i];
}
return dest;
}
all() {
const data = [];
for (let i = 0; i < 9; i++) {
data[i] = this.values[i];
}
return data;
}
row(index) {
return [
this.values[index * 3 + 0],
this.values[index * 3 + 1],
this.values[index * 3 + 2]
];
}
col(index) {
return [this.values[index], this.values[index + 3], this.values[index + 6]];
}
equals(matrix, threshold = epsilon) {
for (let i = 0; i < 9; i++) {
if (Math.abs(this.values[i] - matrix.at(i)) > threshold) {
return false;
}
}
return true;
}
determinant() {
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a10 = this.values[3];
const a11 = this.values[4];
const a12 = this.values[5];
const a20 = this.values[6];
const a21 = this.values[7];
const a22 = this.values[8];
const det01 = a22 * a11 - a12 * a21;
const det11 = -a22 * a10 + a12 * a20;
const det21 = a21 * a10 - a11 * a20;
return a00 * det01 + a01 * det11 + a02 * det21;
}
setIdentity() {
this.values[0] = 1;
this.values[1] = 0;
this.values[2] = 0;
this.values[3] = 0;
this.values[4] = 1;
this.values[5] = 0;
this.values[6] = 0;
this.values[7] = 0;
this.values[8] = 1;
return this;
}
transpose() {
const temp01 = this.values[1];
const temp02 = this.values[2];
const temp12 = this.values[5];
this.values[1] = this.values[3];
this.values[2] = this.values[6];
this.values[3] = temp01;
this.values[5] = this.values[7];
this.values[6] = temp02;
this.values[7] = temp12;
return this;
}
inverse() {
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a10 = this.values[3];
const a11 = this.values[4];
const a12 = this.values[5];
const a20 = this.values[6];
const a21 = this.values[7];
const a22 = this.values[8];
const det01 = a22 * a11 - a12 * a21;
const det11 = -a22 * a10 + a12 * a20;
const det21 = a21 * a10 - a11 * a20;
let det = a00 * det01 + a01 * det11 + a02 * det21;
if (!det) {
return null;
}
det = 1.0 / det;
this.values[0] = det01 * det;
this.values[1] = (-a22 * a01 + a02 * a21) * det;
this.values[2] = (a12 * a01 - a02 * a11) * det;
this.values[3] = det11 * det;
this.values[4] = (a22 * a00 - a02 * a20) * det;
this.values[5] = (-a12 * a00 + a02 * a10) * det;
this.values[6] = det21 * det;
this.values[7] = (-a21 * a00 + a01 * a20) * det;
this.values[8] = (a11 * a00 - a01 * a10) * det;
return this;
}
multiply(matrix) {
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a10 = this.values[3];
const a11 = this.values[4];
const a12 = this.values[5];
const a20 = this.values[6];
const a21 = this.values[7];
const a22 = this.values[8];
const b00 = matrix.at(0);
const b01 = matrix.at(1);
const b02 = matrix.at(2);
const b10 = matrix.at(3);
const b11 = matrix.at(4);
const b12 = matrix.at(5);
const b20 = matrix.at(6);
const b21 = matrix.at(7);
const b22 = matrix.at(8);
this.values[0] = b00 * a00 + b01 * a10 + b02 * a20;
this.values[1] = b00 * a01 + b01 * a11 + b02 * a21;
this.values[2] = b00 * a02 + b01 * a12 + b02 * a22;
this.values[3] = b10 * a00 + b11 * a10 + b12 * a20;
this.values[4] = b10 * a01 + b11 * a11 + b12 * a21;
this.values[5] = b10 * a02 + b11 * a12 + b12 * a22;
this.values[6] = b20 * a00 + b21 * a10 + b22 * a20;
this.values[7] = b20 * a01 + b21 * a11 + b22 * a21;
this.values[8] = b20 * a02 + b21 * a12 + b22 * a22;
return this;
}
multiplyVec2(vector, result) {
const x = vector.x;
const y = vector.y;
if (result) {
result.xy = [
x * this.values[0] + y * this.values[3] + this.values[6],
x * this.values[1] + y * this.values[4] + this.values[7]
];
return result;
}
else {
return new vec2([
x * this.values[0] + y * this.values[3] + this.values[6],
x * this.values[1] + y * this.values[4] + this.values[7]
]);
}
}
multiplyVec3(vector, result) {
const x = vector.x;
const y = vector.y;
const z = vector.z;
if (result) {
result.xyz = [
x * this.values[0] + y * this.values[3] + z * this.values[6],
x * this.values[1] + y * this.values[4] + z * this.values[7],
x * this.values[2] + y * this.values[5] + z * this.values[8]
];
return result;
}
else {
return new vec3([
x * this.values[0] + y * this.values[3] + z * this.values[6],
x * this.values[1] + y * this.values[4] + z * this.values[7],
x * this.values[2] + y * this.values[5] + z * this.values[8]
]);
}
}
toMat4(result) {
if (result) {
result.init([
this.values[0],
this.values[1],
this.values[2],
0,
this.values[3],
this.values[4],
this.values[5],
0,
this.values[6],
this.values[7],
this.values[8],
0,
0,
0,
0,
1
]);
return result;
}
else {
return new mat4([
this.values[0],
this.values[1],
this.values[2],
0,
this.values[3],
this.values[4],
this.values[5],
0,
this.values[6],
this.values[7],
this.values[8],
0,
0,
0,
0,
1
]);
}
}
toQuat() {
const m00 = this.values[0];
const m01 = this.values[1];
const m02 = this.values[2];
const m10 = this.values[3];
const m11 = this.values[4];
const m12 = this.values[5];
const m20 = this.values[6];
const m21 = this.values[7];
const m22 = this.values[8];
const fourXSquaredMinus1 = m00 - m11 - m22;
const fourYSquaredMinus1 = m11 - m00 - m22;
const fourZSquaredMinus1 = m22 - m00 - m11;
const fourWSquaredMinus1 = m00 + m11 + m22;
let biggestIndex = 0;
let fourBiggestSquaredMinus1 = fourWSquaredMinus1;
if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) {
fourBiggestSquaredMinus1 = fourXSquaredMinus1;
biggestIndex = 1;
}
if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) {
fourBiggestSquaredMinus1 = fourYSquaredMinus1;
biggestIndex = 2;
}
if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) {
fourBiggestSquaredMinus1 = fourZSquaredMinus1;
biggestIndex = 3;
}
const biggestVal = Math.sqrt(fourBiggestSquaredMinus1 + 1) * 0.5;
const mult = 0.25 / biggestVal;
const result = new quat();
switch (biggestIndex) {
case 0:
result.w = biggestVal;
result.x = (m12 - m21) * mult;
result.y = (m20 - m02) * mult;
result.z = (m01 - m10) * mult;
break;
case 1:
result.w = (m12 - m21) * mult;
result.x = biggestVal;
result.y = (m01 + m10) * mult;
result.z = (m20 + m02) * mult;
break;
case 2:
result.w = (m20 - m02) * mult;
result.x = (m01 + m10) * mult;
result.y = biggestVal;
result.z = (m12 + m21) * mult;
break;
case 3:
result.w = (m01 - m10) * mult;
result.x = (m20 + m02) * mult;
result.y = (m12 + m21) * mult;
result.z = biggestVal;
break;
}
return result;
}
rotate(angle, axis) {
let x = axis.x;
let y = axis.y;
let z = axis.z;
let length = Math.sqrt(x * x + y * y + z * z);
if (!length) {
return null;
}
if (length !== 1) {
length = 1 / length;
x *= length;
y *= length;
z *= length;
}
const s = Math.sin(angle);
const c = Math.cos(angle);
const t = 1.0 - c;
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a10 = this.values[4];
const a11 = this.values[5];
const a12 = this.values[6];
const a20 = this.values[8];
const a21 = this.values[9];
const a22 = this.values[10];
const b00 = x * x * t + c;
const b01 = y * x * t + z * s;
const b02 = z * x * t - y * s;
const b10 = x * y * t - z * s;
const b11 = y * y * t + c;
const b12 = z * y * t + x * s;
const b20 = x * z * t + y * s;
const b21 = y * z * t - x * s;
const b22 = z * z * t + c;
this.values[0] = a00 * b00 + a10 * b01 + a20 * b02;
this.values[1] = a01 * b00 + a11 * b01 + a21 * b02;
this.values[2] = a02 * b00 + a12 * b01 + a22 * b02;
this.values[3] = a00 * b10 + a10 * b11 + a20 * b12;
this.values[4] = a01 * b10 + a11 * b11 + a21 * b12;
this.values[5] = a02 * b10 + a12 * b11 + a22 * b12;
this.values[6] = a00 * b20 + a10 * b21 + a20 * b22;
this.values[7] = a01 * b20 + a11 * b21 + a21 * b22;
this.values[8] = a02 * b20 + a12 * b21 + a22 * b22;
return this;
}
static product(m1, m2, result) {
const a00 = m1.at(0);
const a01 = m1.at(1);
const a02 = m1.at(2);
const a10 = m1.at(3);
const a11 = m1.at(4);
const a12 = m1.at(5);
const a20 = m1.at(6);
const a21 = m1.at(7);
const a22 = m1.at(8);
const b00 = m2.at(0);
const b01 = m2.at(1);
const b02 = m2.at(2);
const b10 = m2.at(3);
const b11 = m2.at(4);
const b12 = m2.at(5);
const b20 = m2.at(6);
const b21 = m2.at(7);
const b22 = m2.at(8);
if (result) {
result.init([
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22
]);
return result;
}
else {
return new mat3([
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22
]);
}
}
}
mat3.identity = new mat3().setIdentity();

View File

@@ -0,0 +1,33 @@
import mat3 from './mat3';
import vec3 from './vec3';
import vec4 from './vec4';
export default class mat4 {
constructor(values?: number[]);
private values;
static readonly identity: mat4;
at(index: number): number;
init(values: number[]): mat4;
reset(): void;
copy(dest?: mat4): mat4;
all(): number[];
row(index: number): number[];
col(index: number): number[];
equals(matrix: mat4, threshold?: number): boolean;
determinant(): number;
setIdentity(): mat4;
transpose(): mat4;
inverse(): mat4;
multiply(matrix: mat4): mat4;
multiplyVec3(vector: vec3): vec3;
multiplyVec4(vector: vec4, dest?: vec4): vec4;
toMat3(): mat3;
toInverseMat3(): mat3;
translate(vector: vec3): mat4;
scale(vector: vec3): mat4;
rotate(angle: number, axis: vec3): mat4;
static frustum(left: number, right: number, bottom: number, top: number, near: number, far: number): mat4;
static perspective(fov: number, aspect: number, near: number, far: number): mat4;
static orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): mat4;
static lookAt(position: vec3, target: vec3, up?: vec3): mat4;
static product(m1: mat4, m2: mat4, result: mat4): mat4;
}

579
framework/resonator/vendor/tsm/mat4.js vendored Normal file
View File

@@ -0,0 +1,579 @@
import mat3 from './mat3';
import vec3 from './vec3';
import vec4 from './vec4';
import { epsilon } from './constants';
export default class mat4 {
constructor(values) {
this.values = new Float32Array(16);
if (values !== undefined) {
this.init(values);
}
}
at(index) {
return this.values[index];
}
init(values) {
for (let i = 0; i < 16; i++) {
this.values[i] = values[i];
}
return this;
}
reset() {
for (let i = 0; i < 16; i++) {
this.values[i] = 0;
}
}
copy(dest) {
if (!dest) {
dest = new mat4();
}
for (let i = 0; i < 16; i++) {
dest.values[i] = this.values[i];
}
return dest;
}
all() {
const data = [];
for (let i = 0; i < 16; i++) {
data[i] = this.values[i];
}
return data;
}
row(index) {
return [
this.values[index * 4 + 0],
this.values[index * 4 + 1],
this.values[index * 4 + 2],
this.values[index * 4 + 3]
];
}
col(index) {
return [
this.values[index],
this.values[index + 4],
this.values[index + 8],
this.values[index + 12]
];
}
equals(matrix, threshold = epsilon) {
for (let i = 0; i < 16; i++) {
if (Math.abs(this.values[i] - matrix.at(i)) > threshold) {
return false;
}
}
return true;
}
determinant() {
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a03 = this.values[3];
const a10 = this.values[4];
const a11 = this.values[5];
const a12 = this.values[6];
const a13 = this.values[7];
const a20 = this.values[8];
const a21 = this.values[9];
const a22 = this.values[10];
const a23 = this.values[11];
const a30 = this.values[12];
const a31 = this.values[13];
const a32 = this.values[14];
const a33 = this.values[15];
const det00 = a00 * a11 - a01 * a10;
const det01 = a00 * a12 - a02 * a10;
const det02 = a00 * a13 - a03 * a10;
const det03 = a01 * a12 - a02 * a11;
const det04 = a01 * a13 - a03 * a11;
const det05 = a02 * a13 - a03 * a12;
const det06 = a20 * a31 - a21 * a30;
const det07 = a20 * a32 - a22 * a30;
const det08 = a20 * a33 - a23 * a30;
const det09 = a21 * a32 - a22 * a31;
const det10 = a21 * a33 - a23 * a31;
const det11 = a22 * a33 - a23 * a32;
return (det00 * det11 -
det01 * det10 +
det02 * det09 +
det03 * det08 -
det04 * det07 +
det05 * det06);
}
setIdentity() {
this.values[0] = 1;
this.values[1] = 0;
this.values[2] = 0;
this.values[3] = 0;
this.values[4] = 0;
this.values[5] = 1;
this.values[6] = 0;
this.values[7] = 0;
this.values[8] = 0;
this.values[9] = 0;
this.values[10] = 1;
this.values[11] = 0;
this.values[12] = 0;
this.values[13] = 0;
this.values[14] = 0;
this.values[15] = 1;
return this;
}
transpose() {
const temp01 = this.values[1];
const temp02 = this.values[2];
const temp03 = this.values[3];
const temp12 = this.values[6];
const temp13 = this.values[7];
const temp23 = this.values[11];
this.values[1] = this.values[4];
this.values[2] = this.values[8];
this.values[3] = this.values[12];
this.values[4] = temp01;
this.values[6] = this.values[9];
this.values[7] = this.values[13];
this.values[8] = temp02;
this.values[9] = temp12;
this.values[11] = this.values[14];
this.values[12] = temp03;
this.values[13] = temp13;
this.values[14] = temp23;
return this;
}
inverse() {
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a03 = this.values[3];
const a10 = this.values[4];
const a11 = this.values[5];
const a12 = this.values[6];
const a13 = this.values[7];
const a20 = this.values[8];
const a21 = this.values[9];
const a22 = this.values[10];
const a23 = this.values[11];
const a30 = this.values[12];
const a31 = this.values[13];
const a32 = this.values[14];
const a33 = this.values[15];
const det00 = a00 * a11 - a01 * a10;
const det01 = a00 * a12 - a02 * a10;
const det02 = a00 * a13 - a03 * a10;
const det03 = a01 * a12 - a02 * a11;
const det04 = a01 * a13 - a03 * a11;
const det05 = a02 * a13 - a03 * a12;
const det06 = a20 * a31 - a21 * a30;
const det07 = a20 * a32 - a22 * a30;
const det08 = a20 * a33 - a23 * a30;
const det09 = a21 * a32 - a22 * a31;
const det10 = a21 * a33 - a23 * a31;
const det11 = a22 * a33 - a23 * a32;
let det = det00 * det11 -
det01 * det10 +
det02 * det09 +
det03 * det08 -
det04 * det07 +
det05 * det06;
if (!det) {
return null;
}
det = 1.0 / det;
this.values[0] = (a11 * det11 - a12 * det10 + a13 * det09) * det;
this.values[1] = (-a01 * det11 + a02 * det10 - a03 * det09) * det;
this.values[2] = (a31 * det05 - a32 * det04 + a33 * det03) * det;
this.values[3] = (-a21 * det05 + a22 * det04 - a23 * det03) * det;
this.values[4] = (-a10 * det11 + a12 * det08 - a13 * det07) * det;
this.values[5] = (a00 * det11 - a02 * det08 + a03 * det07) * det;
this.values[6] = (-a30 * det05 + a32 * det02 - a33 * det01) * det;
this.values[7] = (a20 * det05 - a22 * det02 + a23 * det01) * det;
this.values[8] = (a10 * det10 - a11 * det08 + a13 * det06) * det;
this.values[9] = (-a00 * det10 + a01 * det08 - a03 * det06) * det;
this.values[10] = (a30 * det04 - a31 * det02 + a33 * det00) * det;
this.values[11] = (-a20 * det04 + a21 * det02 - a23 * det00) * det;
this.values[12] = (-a10 * det09 + a11 * det07 - a12 * det06) * det;
this.values[13] = (a00 * det09 - a01 * det07 + a02 * det06) * det;
this.values[14] = (-a30 * det03 + a31 * det01 - a32 * det00) * det;
this.values[15] = (a20 * det03 - a21 * det01 + a22 * det00) * det;
return this;
}
multiply(matrix) {
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a03 = this.values[3];
const a10 = this.values[4];
const a11 = this.values[5];
const a12 = this.values[6];
const a13 = this.values[7];
const a20 = this.values[8];
const a21 = this.values[9];
const a22 = this.values[10];
const a23 = this.values[11];
const a30 = this.values[12];
const a31 = this.values[13];
const a32 = this.values[14];
const a33 = this.values[15];
let b0 = matrix.at(0);
let b1 = matrix.at(1);
let b2 = matrix.at(2);
let b3 = matrix.at(3);
this.values[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
this.values[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
this.values[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
this.values[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = matrix.at(4);
b1 = matrix.at(5);
b2 = matrix.at(6);
b3 = matrix.at(7);
this.values[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
this.values[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
this.values[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
this.values[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = matrix.at(8);
b1 = matrix.at(9);
b2 = matrix.at(10);
b3 = matrix.at(11);
this.values[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
this.values[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
this.values[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
this.values[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = matrix.at(12);
b1 = matrix.at(13);
b2 = matrix.at(14);
b3 = matrix.at(15);
this.values[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
this.values[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
this.values[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
this.values[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
return this;
}
multiplyVec3(vector) {
const x = vector.x;
const y = vector.y;
const z = vector.z;
return new vec3([
this.values[0] * x +
this.values[4] * y +
this.values[8] * z +
this.values[12],
this.values[1] * x +
this.values[5] * y +
this.values[9] * z +
this.values[13],
this.values[2] * x +
this.values[6] * y +
this.values[10] * z +
this.values[14]
]);
}
multiplyVec4(vector, dest) {
if (!dest) {
dest = new vec4();
}
const x = vector.x;
const y = vector.y;
const z = vector.z;
const w = vector.w;
dest.x =
this.values[0] * x +
this.values[4] * y +
this.values[8] * z +
this.values[12] * w;
dest.y =
this.values[1] * x +
this.values[5] * y +
this.values[9] * z +
this.values[13] * w;
dest.z =
this.values[2] * x +
this.values[6] * y +
this.values[10] * z +
this.values[14] * w;
dest.w =
this.values[3] * x +
this.values[7] * y +
this.values[11] * z +
this.values[15] * w;
return dest;
}
toMat3() {
return new mat3([
this.values[0],
this.values[1],
this.values[2],
this.values[4],
this.values[5],
this.values[6],
this.values[8],
this.values[9],
this.values[10]
]);
}
toInverseMat3() {
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a10 = this.values[4];
const a11 = this.values[5];
const a12 = this.values[6];
const a20 = this.values[8];
const a21 = this.values[9];
const a22 = this.values[10];
const det01 = a22 * a11 - a12 * a21;
const det11 = -a22 * a10 + a12 * a20;
const det21 = a21 * a10 - a11 * a20;
let det = a00 * det01 + a01 * det11 + a02 * det21;
if (!det) {
return null;
}
det = 1.0 / det;
return new mat3([
det01 * det,
(-a22 * a01 + a02 * a21) * det,
(a12 * a01 - a02 * a11) * det,
det11 * det,
(a22 * a00 - a02 * a20) * det,
(-a12 * a00 + a02 * a10) * det,
det21 * det,
(-a21 * a00 + a01 * a20) * det,
(a11 * a00 - a01 * a10) * det
]);
}
translate(vector) {
const x = vector.x;
const y = vector.y;
const z = vector.z;
this.values[12] +=
this.values[0] * x + this.values[4] * y + this.values[8] * z;
this.values[13] +=
this.values[1] * x + this.values[5] * y + this.values[9] * z;
this.values[14] +=
this.values[2] * x + this.values[6] * y + this.values[10] * z;
this.values[15] +=
this.values[3] * x + this.values[7] * y + this.values[11] * z;
return this;
}
scale(vector) {
const x = vector.x;
const y = vector.y;
const z = vector.z;
this.values[0] *= x;
this.values[1] *= x;
this.values[2] *= x;
this.values[3] *= x;
this.values[4] *= y;
this.values[5] *= y;
this.values[6] *= y;
this.values[7] *= y;
this.values[8] *= z;
this.values[9] *= z;
this.values[10] *= z;
this.values[11] *= z;
return this;
}
rotate(angle, axis) {
let x = axis.x;
let y = axis.y;
let z = axis.z;
let length = Math.sqrt(x * x + y * y + z * z);
if (!length) {
return null;
}
if (length !== 1) {
length = 1 / length;
x *= length;
y *= length;
z *= length;
}
const s = Math.sin(angle);
const c = Math.cos(angle);
const t = 1.0 - c;
const a00 = this.values[0];
const a01 = this.values[1];
const a02 = this.values[2];
const a03 = this.values[3];
const a10 = this.values[4];
const a11 = this.values[5];
const a12 = this.values[6];
const a13 = this.values[7];
const a20 = this.values[8];
const a21 = this.values[9];
const a22 = this.values[10];
const a23 = this.values[11];
const b00 = x * x * t + c;
const b01 = y * x * t + z * s;
const b02 = z * x * t - y * s;
const b10 = x * y * t - z * s;
const b11 = y * y * t + c;
const b12 = z * y * t + x * s;
const b20 = x * z * t + y * s;
const b21 = y * z * t - x * s;
const b22 = z * z * t + c;
this.values[0] = a00 * b00 + a10 * b01 + a20 * b02;
this.values[1] = a01 * b00 + a11 * b01 + a21 * b02;
this.values[2] = a02 * b00 + a12 * b01 + a22 * b02;
this.values[3] = a03 * b00 + a13 * b01 + a23 * b02;
this.values[4] = a00 * b10 + a10 * b11 + a20 * b12;
this.values[5] = a01 * b10 + a11 * b11 + a21 * b12;
this.values[6] = a02 * b10 + a12 * b11 + a22 * b12;
this.values[7] = a03 * b10 + a13 * b11 + a23 * b12;
this.values[8] = a00 * b20 + a10 * b21 + a20 * b22;
this.values[9] = a01 * b20 + a11 * b21 + a21 * b22;
this.values[10] = a02 * b20 + a12 * b21 + a22 * b22;
this.values[11] = a03 * b20 + a13 * b21 + a23 * b22;
return this;
}
static frustum(left, right, bottom, top, near, far) {
const rl = right - left;
const tb = top - bottom;
const fn = far - near;
return new mat4([
(near * 2) / rl,
0,
0,
0,
0,
(near * 2) / tb,
0,
0,
(right + left) / rl,
(top + bottom) / tb,
-(far + near) / fn,
-1,
0,
0,
-(far * near * 2) / fn,
0
]);
}
static perspective(fov, aspect, near, far) {
const top = near * Math.tan((fov * Math.PI) / 360.0);
const right = top * aspect;
return mat4.frustum(-right, right, -top, top, near, far);
}
static orthographic(left, right, bottom, top, near, far) {
const rl = right - left;
const tb = top - bottom;
const fn = far - near;
return new mat4([
2 / rl,
0,
0,
0,
0,
2 / tb,
0,
0,
0,
0,
-2 / fn,
0,
-(left + right) / rl,
-(top + bottom) / tb,
-(far + near) / fn,
1
]);
}
static lookAt(position, target, up = vec3.up) {
if (position.equals(target)) {
return this.identity;
}
const z = vec3.difference(position, target).normalize();
const x = vec3.cross(up, z).normalize();
const y = vec3.cross(z, x).normalize();
return new mat4([
x.x,
y.x,
z.x,
0,
x.y,
y.y,
z.y,
0,
x.z,
y.z,
z.z,
0,
-vec3.dot(x, position),
-vec3.dot(y, position),
-vec3.dot(z, position),
1
]);
}
static product(m1, m2, result) {
const a00 = m1.at(0);
const a01 = m1.at(1);
const a02 = m1.at(2);
const a03 = m1.at(3);
const a10 = m1.at(4);
const a11 = m1.at(5);
const a12 = m1.at(6);
const a13 = m1.at(7);
const a20 = m1.at(8);
const a21 = m1.at(9);
const a22 = m1.at(10);
const a23 = m1.at(11);
const a30 = m1.at(12);
const a31 = m1.at(13);
const a32 = m1.at(14);
const a33 = m1.at(15);
const b00 = m2.at(0);
const b01 = m2.at(1);
const b02 = m2.at(2);
const b03 = m2.at(3);
const b10 = m2.at(4);
const b11 = m2.at(5);
const b12 = m2.at(6);
const b13 = m2.at(7);
const b20 = m2.at(8);
const b21 = m2.at(9);
const b22 = m2.at(10);
const b23 = m2.at(11);
const b30 = m2.at(12);
const b31 = m2.at(13);
const b32 = m2.at(14);
const b33 = m2.at(15);
if (result) {
result.init([
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33
]);
return result;
}
else {
return new mat4([
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33
]);
}
}
}
mat4.identity = new mat4().setIdentity();

View File

@@ -0,0 +1,47 @@
import mat3 from './mat3';
import mat4 from './mat4';
import vec3 from './vec3';
export default class quat {
get x(): number;
get y(): number;
get z(): number;
get w(): number;
get xy(): [number, number];
get xyz(): [number, number, number];
get xyzw(): [number, number, number, number];
set x(value: number);
set y(value: number);
set z(value: number);
set w(value: number);
set xy(values: [number, number]);
set xyz(values: [number, number, number]);
set xyzw(values: [number, number, number, number]);
constructor(values?: [number, number, number, number]);
private values;
static readonly identity: quat;
at(index: number): number;
reset(): void;
copy(dest?: quat): quat;
roll(): number;
pitch(): number;
yaw(): number;
equals(vector: quat, threshold?: number): boolean;
setIdentity(): quat;
calculateW(): quat;
inverse(): quat;
conjugate(): quat;
length(): number;
normalize(dest?: quat): quat;
add(other: quat): quat;
multiply(other: quat): quat;
multiplyVec3(vector: vec3, dest?: vec3): vec3;
toMat3(dest?: mat3): mat3;
toMat4(dest?: mat4): mat4;
static dot(q1: quat, q2: quat): number;
static sum(q1: quat, q2: quat, dest?: quat): quat;
static product(q1: quat, q2: quat, dest?: quat): quat;
static cross(q1: quat, q2: quat, dest?: quat): quat;
static shortMix(q1: quat, q2: quat, time: number, dest?: quat): quat;
static mix(q1: quat, q2: quat, time: number, dest?: quat): quat;
static fromAxisAngle(axis: vec3, angle: number, dest?: quat): quat;
}

404
framework/resonator/vendor/tsm/quat.js vendored Normal file
View File

@@ -0,0 +1,404 @@
import mat3 from './mat3';
import mat4 from './mat4';
import vec3 from './vec3';
import { epsilon } from './constants';
export default class quat {
constructor(values) {
this.values = new Float32Array(4);
if (values !== undefined) {
this.xyzw = values;
}
}
get x() {
return this.values[0];
}
get y() {
return this.values[1];
}
get z() {
return this.values[2];
}
get w() {
return this.values[3];
}
get xy() {
return [this.values[0], this.values[1]];
}
get xyz() {
return [this.values[0], this.values[1], this.values[2]];
}
get xyzw() {
return [this.values[0], this.values[1], this.values[2], this.values[3]];
}
set x(value) {
this.values[0] = value;
}
set y(value) {
this.values[1] = value;
}
set z(value) {
this.values[2] = value;
}
set w(value) {
this.values[3] = value;
}
set xy(values) {
this.values[0] = values[0];
this.values[1] = values[1];
}
set xyz(values) {
this.values[0] = values[0];
this.values[1] = values[1];
this.values[2] = values[2];
}
set xyzw(values) {
this.values[0] = values[0];
this.values[1] = values[1];
this.values[2] = values[2];
this.values[3] = values[3];
}
at(index) {
return this.values[index];
}
reset() {
for (let i = 0; i < 4; i++) {
this.values[i] = 0;
}
}
copy(dest) {
if (!dest) {
dest = new quat();
}
for (let i = 0; i < 4; i++) {
dest.values[i] = this.values[i];
}
return dest;
}
roll() {
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
return Math.atan2(2.0 * (x * y + w * z), w * w + x * x - y * y - z * z);
}
pitch() {
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
return Math.atan2(2.0 * (y * z + w * x), w * w - x * x - y * y + z * z);
}
yaw() {
return Math.asin(2.0 * (this.x * this.z - this.w * this.y));
}
equals(vector, threshold = epsilon) {
for (let i = 0; i < 4; i++) {
if (Math.abs(this.values[i] - vector.at(i)) > threshold) {
return false;
}
}
return true;
}
setIdentity() {
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 1;
return this;
}
calculateW() {
const x = this.x;
const y = this.y;
const z = this.z;
this.w = -Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z));
return this;
}
inverse() {
const dot = quat.dot(this, this);
if (!dot) {
this.xyzw = [0, 0, 0, 0];
return this;
}
const invDot = dot ? 1.0 / dot : 0;
this.x *= -invDot;
this.y *= -invDot;
this.z *= -invDot;
this.w *= invDot;
return this;
}
conjugate() {
this.values[0] *= -1;
this.values[1] *= -1;
this.values[2] *= -1;
return this;
}
length() {
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
return Math.sqrt(x * x + y * y + z * z + w * w);
}
normalize(dest) {
if (!dest) {
dest = this;
}
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
let length = Math.sqrt(x * x + y * y + z * z + w * w);
if (!length) {
dest.x = 0;
dest.y = 0;
dest.z = 0;
dest.w = 0;
return dest;
}
length = 1 / length;
dest.x = x * length;
dest.y = y * length;
dest.z = z * length;
dest.w = w * length;
return dest;
}
add(other) {
for (let i = 0; i < 4; i++) {
this.values[i] += other.at(i);
}
return this;
}
multiply(other) {
const q1x = this.values[0];
const q1y = this.values[1];
const q1z = this.values[2];
const q1w = this.values[3];
const q2x = other.x;
const q2y = other.y;
const q2z = other.z;
const q2w = other.w;
this.x = q1x * q2w + q1w * q2x + q1y * q2z - q1z * q2y;
this.y = q1y * q2w + q1w * q2y + q1z * q2x - q1x * q2z;
this.z = q1z * q2w + q1w * q2z + q1x * q2y - q1y * q2x;
this.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
return this;
}
multiplyVec3(vector, dest) {
if (!dest) {
dest = new vec3();
}
const x = vector.x;
const y = vector.y;
const z = vector.z;
const qx = this.x;
const qy = this.y;
const qz = this.z;
const qw = this.w;
const ix = qw * x + qy * z - qz * y;
const iy = qw * y + qz * x - qx * z;
const iz = qw * z + qx * y - qy * x;
const iw = -qx * x - qy * y - qz * z;
dest.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
dest.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
dest.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
return dest;
}
toMat3(dest) {
if (!dest) {
dest = new mat3();
}
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
const x2 = x + x;
const y2 = y + y;
const z2 = z + z;
const xx = x * x2;
const xy = x * y2;
const xz = x * z2;
const yy = y * y2;
const yz = y * z2;
const zz = z * z2;
const wx = w * x2;
const wy = w * y2;
const wz = w * z2;
dest.init([
1 - (yy + zz),
xy + wz,
xz - wy,
xy - wz,
1 - (xx + zz),
yz + wx,
xz + wy,
yz - wx,
1 - (xx + yy)
]);
return dest;
}
toMat4(dest) {
if (!dest) {
dest = new mat4();
}
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
const x2 = x + x;
const y2 = y + y;
const z2 = z + z;
const xx = x * x2;
const xy = x * y2;
const xz = x * z2;
const yy = y * y2;
const yz = y * z2;
const zz = z * z2;
const wx = w * x2;
const wy = w * y2;
const wz = w * z2;
dest.init([
1 - (yy + zz),
xy + wz,
xz - wy,
0,
xy - wz,
1 - (xx + zz),
yz + wx,
0,
xz + wy,
yz - wx,
1 - (xx + yy),
0,
0,
0,
0,
1
]);
return dest;
}
static dot(q1, q2) {
return q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
}
static sum(q1, q2, dest) {
if (!dest) {
dest = new quat();
}
dest.x = q1.x + q2.x;
dest.y = q1.y + q2.y;
dest.z = q1.z + q2.z;
dest.w = q1.w + q2.w;
return dest;
}
static product(q1, q2, dest) {
if (!dest) {
dest = new quat();
}
const q1x = q1.x;
const q1y = q1.y;
const q1z = q1.z;
const q1w = q1.w;
const q2x = q2.x;
const q2y = q2.y;
const q2z = q2.z;
const q2w = q2.w;
dest.x = q1x * q2w + q1w * q2x + q1y * q2z - q1z * q2y;
dest.y = q1y * q2w + q1w * q2y + q1z * q2x - q1x * q2z;
dest.z = q1z * q2w + q1w * q2z + q1x * q2y - q1y * q2x;
dest.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
return dest;
}
static cross(q1, q2, dest) {
if (!dest) {
dest = new quat();
}
const q1x = q1.x;
const q1y = q1.y;
const q1z = q1.z;
const q1w = q1.w;
const q2x = q2.x;
const q2y = q2.y;
const q2z = q2.z;
const q2w = q2.w;
dest.x = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x;
dest.y = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
dest.z = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y;
dest.w = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z;
return dest;
}
static shortMix(q1, q2, time, dest) {
if (!dest) {
dest = new quat();
}
if (time <= 0.0) {
dest.xyzw = q1.xyzw;
return dest;
}
else if (time >= 1.0) {
dest.xyzw = q2.xyzw;
return dest;
}
let cos = quat.dot(q1, q2);
const q2a = q2.copy();
if (cos < 0.0) {
q2a.inverse();
cos = -cos;
}
let k0;
let k1;
if (cos > 0.9999) {
k0 = 1 - time;
k1 = 0 + time;
}
else {
const sin = Math.sqrt(1 - cos * cos);
const angle = Math.atan2(sin, cos);
const oneOverSin = 1 / sin;
k0 = Math.sin((1 - time) * angle) * oneOverSin;
k1 = Math.sin((0 + time) * angle) * oneOverSin;
}
dest.x = k0 * q1.x + k1 * q2a.x;
dest.y = k0 * q1.y + k1 * q2a.y;
dest.z = k0 * q1.z + k1 * q2a.z;
dest.w = k0 * q1.w + k1 * q2a.w;
return dest;
}
static mix(q1, q2, time, dest) {
if (!dest) {
dest = new quat();
}
const cosHalfTheta = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
if (Math.abs(cosHalfTheta) >= 1.0) {
dest.xyzw = q1.xyzw;
return dest;
}
const halfTheta = Math.acos(cosHalfTheta);
const sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta);
if (Math.abs(sinHalfTheta) < 0.001) {
dest.x = q1.x * 0.5 + q2.x * 0.5;
dest.y = q1.y * 0.5 + q2.y * 0.5;
dest.z = q1.z * 0.5 + q2.z * 0.5;
dest.w = q1.w * 0.5 + q2.w * 0.5;
return dest;
}
const ratioA = Math.sin((1 - time) * halfTheta) / sinHalfTheta;
const ratioB = Math.sin(time * halfTheta) / sinHalfTheta;
dest.x = q1.x * ratioA + q2.x * ratioB;
dest.y = q1.y * ratioA + q2.y * ratioB;
dest.z = q1.z * ratioA + q2.z * ratioB;
dest.w = q1.w * ratioA + q2.w * ratioB;
return dest;
}
static fromAxisAngle(axis, angle, dest) {
if (!dest) {
dest = new quat();
}
angle *= 0.5;
const sin = Math.sin(angle);
dest.x = axis.x * sin;
dest.y = axis.y * sin;
dest.z = axis.z * sin;
dest.w = Math.cos(angle);
return dest;
}
}
quat.identity = new quat().setIdentity();

17
framework/resonator/vendor/tsm/tsm.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
import mat2 from './mat2';
import mat3 from './mat3';
import mat4 from './mat4';
import quat from './quat';
import vec2 from './vec2';
import vec3 from './vec3';
import vec4 from './vec4';
declare const _default: {
vec2: typeof vec2;
vec3: typeof vec3;
vec4: typeof vec4;
mat2: typeof mat2;
mat3: typeof mat3;
mat4: typeof mat4;
quat: typeof quat;
};
export default _default;

40
framework/resonator/vendor/tsm/tsm.js vendored Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2012, 2018 Matthias Ferch
*
* Project homepage: https://github.com/matthiasferch/tsm
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*/
import mat2 from './mat2';
import mat3 from './mat3';
import mat4 from './mat4';
import quat from './quat';
import vec2 from './vec2';
import vec3 from './vec3';
import vec4 from './vec4';
export default {
vec2,
vec3,
vec4,
mat2,
mat3,
mat4,
quat
};

View File

@@ -0,0 +1,40 @@
import mat2 from './mat2';
import mat3 from './mat3';
import vec3 from './vec3';
export default class vec2 {
get x(): number;
get y(): number;
get xy(): [number, number];
set x(value: number);
set y(value: number);
set xy(values: [number, number]);
constructor(values?: [number, number]);
private values;
static readonly zero: vec2;
static readonly one: vec2;
at(index: number): number;
reset(): void;
copy(dest?: vec2): vec2;
negate(dest?: vec2): vec2;
equals(vector: vec2, threshold?: number): boolean;
length(): number;
squaredLength(): number;
add(vector: vec2): vec2;
subtract(vector: vec2): vec2;
multiply(vector: vec2): vec2;
divide(vector: vec2): vec2;
scale(value: number, dest?: vec2): vec2;
normalize(dest?: vec2): vec2;
multiplyMat2(matrix: mat2, dest?: vec2): vec2;
multiplyMat3(matrix: mat3, dest?: vec2): vec2;
static cross(vector: vec2, vector2: vec2, dest?: vec3): vec3;
static dot(vector: vec2, vector2: vec2): number;
static distance(vector: vec2, vector2: vec2): number;
static squaredDistance(vector: vec2, vector2: vec2): number;
static direction(vector: vec2, vector2: vec2, dest?: vec2): vec2;
static mix(vector: vec2, vector2: vec2, time: number, dest?: vec2): vec2;
static sum(vector: vec2, vector2: vec2, dest?: vec2): vec2;
static difference(vector: vec2, vector2: vec2, dest?: vec2): vec2;
static product(vector: vec2, vector2: vec2, dest?: vec2): vec2;
static quotient(vector: vec2, vector2: vec2, dest?: vec2): vec2;
}

215
framework/resonator/vendor/tsm/vec2.js vendored Normal file
View File

@@ -0,0 +1,215 @@
import vec3 from './vec3';
import { epsilon } from './constants';
export default class vec2 {
constructor(values) {
this.values = new Float32Array(2);
if (values !== undefined) {
this.xy = values;
}
}
get x() {
return this.values[0];
}
get y() {
return this.values[1];
}
get xy() {
return [this.values[0], this.values[1]];
}
set x(value) {
this.values[0] = value;
}
set y(value) {
this.values[1] = value;
}
set xy(values) {
this.values[0] = values[0];
this.values[1] = values[1];
}
at(index) {
return this.values[index];
}
reset() {
this.x = 0;
this.y = 0;
}
copy(dest) {
if (!dest) {
dest = new vec2();
}
dest.x = this.x;
dest.y = this.y;
return dest;
}
negate(dest) {
if (!dest) {
dest = this;
}
dest.x = -this.x;
dest.y = -this.y;
return dest;
}
equals(vector, threshold = epsilon) {
if (Math.abs(this.x - vector.x) > threshold) {
return false;
}
if (Math.abs(this.y - vector.y) > threshold) {
return false;
}
return true;
}
length() {
return Math.sqrt(this.squaredLength());
}
squaredLength() {
const x = this.x;
const y = this.y;
return x * x + y * y;
}
add(vector) {
this.x += vector.x;
this.y += vector.y;
return this;
}
subtract(vector) {
this.x -= vector.x;
this.y -= vector.y;
return this;
}
multiply(vector) {
this.x *= vector.x;
this.y *= vector.y;
return this;
}
divide(vector) {
this.x /= vector.x;
this.y /= vector.y;
return this;
}
scale(value, dest) {
if (!dest) {
dest = this;
}
dest.x *= value;
dest.y *= value;
return dest;
}
normalize(dest) {
if (!dest) {
dest = this;
}
let length = this.length();
if (length === 1) {
return this;
}
if (length === 0) {
dest.x = 0;
dest.y = 0;
return dest;
}
length = 1.0 / length;
dest.x *= length;
dest.y *= length;
return dest;
}
multiplyMat2(matrix, dest) {
if (!dest) {
dest = this;
}
return matrix.multiplyVec2(this, dest);
}
multiplyMat3(matrix, dest) {
if (!dest) {
dest = this;
}
return matrix.multiplyVec2(this, dest);
}
static cross(vector, vector2, dest) {
if (!dest) {
dest = new vec3();
}
const x = vector.x;
const y = vector.y;
const x2 = vector2.x;
const y2 = vector2.y;
const z = x * y2 - y * x2;
dest.x = 0;
dest.y = 0;
dest.z = z;
return dest;
}
static dot(vector, vector2) {
return vector.x * vector2.x + vector.y * vector2.y;
}
static distance(vector, vector2) {
return Math.sqrt(this.squaredDistance(vector, vector2));
}
static squaredDistance(vector, vector2) {
const x = vector2.x - vector.x;
const y = vector2.y - vector.y;
return x * x + y * y;
}
static direction(vector, vector2, dest) {
if (!dest) {
dest = new vec2();
}
const x = vector.x - vector2.x;
const y = vector.y - vector2.y;
let length = Math.sqrt(x * x + y * y);
if (length === 0) {
dest.x = 0;
dest.y = 0;
return dest;
}
length = 1 / length;
dest.x = x * length;
dest.y = y * length;
return dest;
}
static mix(vector, vector2, time, dest) {
if (!dest) {
dest = new vec2();
}
const x = vector.x;
const y = vector.y;
const x2 = vector2.x;
const y2 = vector2.y;
dest.x = x + time * (x2 - x);
dest.y = y + time * (y2 - y);
return dest;
}
static sum(vector, vector2, dest) {
if (!dest) {
dest = new vec2();
}
dest.x = vector.x + vector2.x;
dest.y = vector.y + vector2.y;
return dest;
}
static difference(vector, vector2, dest) {
if (!dest) {
dest = new vec2();
}
dest.x = vector.x - vector2.x;
dest.y = vector.y - vector2.y;
return dest;
}
static product(vector, vector2, dest) {
if (!dest) {
dest = new vec2();
}
dest.x = vector.x * vector2.x;
dest.y = vector.y * vector2.y;
return dest;
}
static quotient(vector, vector2, dest) {
if (!dest) {
dest = new vec2();
}
dest.x = vector.x / vector2.x;
dest.y = vector.y / vector2.y;
return dest;
}
}
vec2.zero = new vec2([0, 0]);
vec2.one = new vec2([1, 1]);

View File

@@ -0,0 +1,47 @@
import mat3 from './mat3';
import quat from './quat';
export default class vec3 {
get x(): number;
get y(): number;
get z(): number;
get xy(): [number, number];
get xyz(): [number, number, number];
set x(value: number);
set y(value: number);
set z(value: number);
set xy(values: [number, number]);
set xyz(values: [number, number, number]);
constructor(values?: [number, number, number]);
private values;
static readonly zero: vec3;
static readonly one: vec3;
static readonly up: vec3;
static readonly right: vec3;
static readonly forward: vec3;
at(index: number): number;
reset(): void;
copy(dest?: vec3): vec3;
negate(dest?: vec3): vec3;
equals(vector: vec3, threshold?: number): boolean;
length(): number;
squaredLength(): number;
add(vector: vec3): vec3;
subtract(vector: vec3): vec3;
multiply(vector: vec3): vec3;
divide(vector: vec3): vec3;
scale(value: number, dest?: vec3): vec3;
normalize(dest?: vec3): vec3;
multiplyByMat3(matrix: mat3, dest?: vec3): vec3;
multiplyByQuat(quaternion: quat, dest?: vec3): vec3;
toQuat(dest?: quat): quat;
static cross(vector: vec3, vector2: vec3, dest?: vec3): vec3;
static dot(vector: vec3, vector2: vec3): number;
static distance(vector: vec3, vector2: vec3): number;
static squaredDistance(vector: vec3, vector2: vec3): number;
static direction(vector: vec3, vector2: vec3, dest?: vec3): vec3;
static mix(vector: vec3, vector2: vec3, time: number, dest?: vec3): vec3;
static sum(vector: vec3, vector2: vec3, dest?: vec3): vec3;
static difference(vector: vec3, vector2: vec3, dest?: vec3): vec3;
static product(vector: vec3, vector2: vec3, dest?: vec3): vec3;
static quotient(vector: vec3, vector2: vec3, dest?: vec3): vec3;
}

279
framework/resonator/vendor/tsm/vec3.js vendored Normal file
View File

@@ -0,0 +1,279 @@
import quat from './quat';
import { epsilon } from './constants';
export default class vec3 {
constructor(values) {
this.values = new Float32Array(3);
if (values !== undefined) {
this.xyz = values;
}
}
get x() {
return this.values[0];
}
get y() {
return this.values[1];
}
get z() {
return this.values[2];
}
get xy() {
return [this.values[0], this.values[1]];
}
get xyz() {
return [this.values[0], this.values[1], this.values[2]];
}
set x(value) {
this.values[0] = value;
}
set y(value) {
this.values[1] = value;
}
set z(value) {
this.values[2] = value;
}
set xy(values) {
this.values[0] = values[0];
this.values[1] = values[1];
}
set xyz(values) {
this.values[0] = values[0];
this.values[1] = values[1];
this.values[2] = values[2];
}
at(index) {
return this.values[index];
}
reset() {
this.x = 0;
this.y = 0;
this.z = 0;
}
copy(dest) {
if (!dest) {
dest = new vec3();
}
dest.x = this.x;
dest.y = this.y;
dest.z = this.z;
return dest;
}
negate(dest) {
if (!dest) {
dest = this;
}
dest.x = -this.x;
dest.y = -this.y;
dest.z = -this.z;
return dest;
}
equals(vector, threshold = epsilon) {
if (Math.abs(this.x - vector.x) > threshold) {
return false;
}
if (Math.abs(this.y - vector.y) > threshold) {
return false;
}
if (Math.abs(this.z - vector.z) > threshold) {
return false;
}
return true;
}
length() {
return Math.sqrt(this.squaredLength());
}
squaredLength() {
const x = this.x;
const y = this.y;
const z = this.z;
return x * x + y * y + z * z;
}
add(vector) {
this.x += vector.x;
this.y += vector.y;
this.z += vector.z;
return this;
}
subtract(vector) {
this.x -= vector.x;
this.y -= vector.y;
this.z -= vector.z;
return this;
}
multiply(vector) {
this.x *= vector.x;
this.y *= vector.y;
this.z *= vector.z;
return this;
}
divide(vector) {
this.x /= vector.x;
this.y /= vector.y;
this.z /= vector.z;
return this;
}
scale(value, dest) {
if (!dest) {
dest = this;
}
dest.x *= value;
dest.y *= value;
dest.z *= value;
return dest;
}
normalize(dest) {
if (!dest) {
dest = this;
}
let length = this.length();
if (length === 1) {
return this;
}
if (length === 0) {
dest.x = 0;
dest.y = 0;
dest.z = 0;
return dest;
}
length = 1.0 / length;
dest.x *= length;
dest.y *= length;
dest.z *= length;
return dest;
}
multiplyByMat3(matrix, dest) {
if (!dest) {
dest = this;
}
return matrix.multiplyVec3(this, dest);
}
multiplyByQuat(quaternion, dest) {
if (!dest) {
dest = this;
}
return quaternion.multiplyVec3(this, dest);
}
toQuat(dest) {
if (!dest) {
dest = new quat();
}
const c = new vec3();
const s = new vec3();
c.x = Math.cos(this.x * 0.5);
s.x = Math.sin(this.x * 0.5);
c.y = Math.cos(this.y * 0.5);
s.y = Math.sin(this.y * 0.5);
c.z = Math.cos(this.z * 0.5);
s.z = Math.sin(this.z * 0.5);
dest.x = s.x * c.y * c.z - c.x * s.y * s.z;
dest.y = c.x * s.y * c.z + s.x * c.y * s.z;
dest.z = c.x * c.y * s.z - s.x * s.y * c.z;
dest.w = c.x * c.y * c.z + s.x * s.y * s.z;
return dest;
}
static cross(vector, vector2, dest) {
if (!dest) {
dest = new vec3();
}
const x = vector.x;
const y = vector.y;
const z = vector.z;
const x2 = vector2.x;
const y2 = vector2.y;
const z2 = vector2.z;
dest.x = y * z2 - z * y2;
dest.y = z * x2 - x * z2;
dest.z = x * y2 - y * x2;
return dest;
}
static dot(vector, vector2) {
const x = vector.x;
const y = vector.y;
const z = vector.z;
const x2 = vector2.x;
const y2 = vector2.y;
const z2 = vector2.z;
return x * x2 + y * y2 + z * z2;
}
static distance(vector, vector2) {
const x = vector2.x - vector.x;
const y = vector2.y - vector.y;
const z = vector2.z - vector.z;
return Math.sqrt(this.squaredDistance(vector, vector2));
}
static squaredDistance(vector, vector2) {
const x = vector2.x - vector.x;
const y = vector2.y - vector.y;
const z = vector2.z - vector.z;
return x * x + y * y + z * z;
}
static direction(vector, vector2, dest) {
if (!dest) {
dest = new vec3();
}
const x = vector.x - vector2.x;
const y = vector.y - vector2.y;
const z = vector.z - vector2.z;
let length = Math.sqrt(x * x + y * y + z * z);
if (length === 0) {
dest.x = 0;
dest.y = 0;
dest.z = 0;
return dest;
}
length = 1 / length;
dest.x = x * length;
dest.y = y * length;
dest.z = z * length;
return dest;
}
static mix(vector, vector2, time, dest) {
if (!dest) {
dest = new vec3();
}
dest.x = vector.x + time * (vector2.x - vector.x);
dest.y = vector.y + time * (vector2.y - vector.y);
dest.z = vector.z + time * (vector2.z - vector.z);
return dest;
}
static sum(vector, vector2, dest) {
if (!dest) {
dest = new vec3();
}
dest.x = vector.x + vector2.x;
dest.y = vector.y + vector2.y;
dest.z = vector.z + vector2.z;
return dest;
}
static difference(vector, vector2, dest) {
if (!dest) {
dest = new vec3();
}
dest.x = vector.x - vector2.x;
dest.y = vector.y - vector2.y;
dest.z = vector.z - vector2.z;
return dest;
}
static product(vector, vector2, dest) {
if (!dest) {
dest = new vec3();
}
dest.x = vector.x * vector2.x;
dest.y = vector.y * vector2.y;
dest.z = vector.z * vector2.z;
return dest;
}
static quotient(vector, vector2, dest) {
if (!dest) {
dest = new vec3();
}
dest.x = vector.x / vector2.x;
dest.y = vector.y / vector2.y;
dest.z = vector.z / vector2.z;
return dest;
}
}
vec3.zero = new vec3([0, 0, 0]);
vec3.one = new vec3([1, 1, 1]);
vec3.up = new vec3([0, 1, 0]);
vec3.right = new vec3([1, 0, 0]);
vec3.forward = new vec3([0, 0, 1]);

View File

@@ -0,0 +1,54 @@
import mat4 from './mat4';
export default class vec4 {
get x(): number;
get y(): number;
get z(): number;
get w(): number;
get xy(): [number, number];
get xyz(): [number, number, number];
get xyzw(): [number, number, number, number];
set x(value: number);
set y(value: number);
set z(value: number);
set w(value: number);
set xy(values: [number, number]);
set xyz(values: [number, number, number]);
set xyzw(values: [number, number, number, number]);
get r(): number;
get g(): number;
get b(): number;
get a(): number;
get rg(): [number, number];
get rgb(): [number, number, number];
get rgba(): [number, number, number, number];
set r(value: number);
set g(value: number);
set b(value: number);
set a(value: number);
set rg(values: [number, number]);
set rgb(values: [number, number, number]);
set rgba(values: [number, number, number, number]);
constructor(values?: [number, number, number, number]);
private values;
static readonly zero: vec4;
static readonly one: vec4;
at(index: number): number;
reset(): void;
copy(dest?: vec4): vec4;
negate(dest?: vec4): vec4;
equals(vector: vec4, threshold?: number): boolean;
length(): number;
squaredLength(): number;
add(vector: vec4): vec4;
subtract(vector: vec4): vec4;
multiply(vector: vec4): vec4;
divide(vector: vec4): vec4;
scale(value: number, dest?: vec4): vec4;
normalize(dest?: vec4): vec4;
multiplyMat4(matrix: mat4, dest?: vec4): vec4;
static mix(vector: vec4, vector2: vec4, time: number, dest?: vec4): vec4;
static sum(vector: vec4, vector2: vec4, dest?: vec4): vec4;
static difference(vector: vec4, vector2: vec4, dest?: vec4): vec4;
static product(vector: vec4, vector2: vec4, dest?: vec4): vec4;
static quotient(vector: vec4, vector2: vec4, dest?: vec4): vec4;
}

277
framework/resonator/vendor/tsm/vec4.js vendored Normal file
View File

@@ -0,0 +1,277 @@
import { epsilon } from './constants';
export default class vec4 {
constructor(values) {
this.values = new Float32Array(4);
if (values !== undefined) {
this.xyzw = values;
}
}
get x() {
return this.values[0];
}
get y() {
return this.values[1];
}
get z() {
return this.values[2];
}
get w() {
return this.values[3];
}
get xy() {
return [this.values[0], this.values[1]];
}
get xyz() {
return [this.values[0], this.values[1], this.values[2]];
}
get xyzw() {
return [this.values[0], this.values[1], this.values[2], this.values[3]];
}
set x(value) {
this.values[0] = value;
}
set y(value) {
this.values[1] = value;
}
set z(value) {
this.values[2] = value;
}
set w(value) {
this.values[3] = value;
}
set xy(values) {
this.values[0] = values[0];
this.values[1] = values[1];
}
set xyz(values) {
this.values[0] = values[0];
this.values[1] = values[1];
this.values[2] = values[2];
}
set xyzw(values) {
this.values[0] = values[0];
this.values[1] = values[1];
this.values[2] = values[2];
this.values[3] = values[3];
}
get r() {
return this.values[0];
}
get g() {
return this.values[1];
}
get b() {
return this.values[2];
}
get a() {
return this.values[3];
}
get rg() {
return [this.values[0], this.values[1]];
}
get rgb() {
return [this.values[0], this.values[1], this.values[2]];
}
get rgba() {
return [this.values[0], this.values[1], this.values[2], this.values[3]];
}
set r(value) {
this.values[0] = value;
}
set g(value) {
this.values[1] = value;
}
set b(value) {
this.values[2] = value;
}
set a(value) {
this.values[3] = value;
}
set rg(values) {
this.values[0] = values[0];
this.values[1] = values[1];
}
set rgb(values) {
this.values[0] = values[0];
this.values[1] = values[1];
this.values[2] = values[2];
}
set rgba(values) {
this.values[0] = values[0];
this.values[1] = values[1];
this.values[2] = values[2];
this.values[3] = values[3];
}
at(index) {
return this.values[index];
}
reset() {
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 0;
}
copy(dest) {
if (!dest) {
dest = new vec4();
}
dest.x = this.x;
dest.y = this.y;
dest.z = this.z;
dest.w = this.w;
return dest;
}
negate(dest) {
if (!dest) {
dest = this;
}
dest.x = -this.x;
dest.y = -this.y;
dest.z = -this.z;
dest.w = -this.w;
return dest;
}
equals(vector, threshold = epsilon) {
if (Math.abs(this.x - vector.x) > threshold) {
return false;
}
if (Math.abs(this.y - vector.y) > threshold) {
return false;
}
if (Math.abs(this.z - vector.z) > threshold) {
return false;
}
if (Math.abs(this.w - vector.w) > threshold) {
return false;
}
return true;
}
length() {
return Math.sqrt(this.squaredLength());
}
squaredLength() {
const x = this.x;
const y = this.y;
const z = this.z;
const w = this.w;
return x * x + y * y + z * z + w * w;
}
add(vector) {
this.x += vector.x;
this.y += vector.y;
this.z += vector.z;
this.w += vector.w;
return this;
}
subtract(vector) {
this.x -= vector.x;
this.y -= vector.y;
this.z -= vector.z;
this.w -= vector.w;
return this;
}
multiply(vector) {
this.x *= vector.x;
this.y *= vector.y;
this.z *= vector.z;
this.w *= vector.w;
return this;
}
divide(vector) {
this.x /= vector.x;
this.y /= vector.y;
this.z /= vector.z;
this.w /= vector.w;
return this;
}
scale(value, dest) {
if (!dest) {
dest = this;
}
dest.x *= value;
dest.y *= value;
dest.z *= value;
dest.w *= value;
return dest;
}
normalize(dest) {
if (!dest) {
dest = this;
}
let length = this.length();
if (length === 1) {
return this;
}
if (length === 0) {
dest.x *= 0;
dest.y *= 0;
dest.z *= 0;
dest.w *= 0;
return dest;
}
length = 1.0 / length;
dest.x *= length;
dest.y *= length;
dest.z *= length;
dest.w *= length;
return dest;
}
multiplyMat4(matrix, dest) {
if (!dest) {
dest = this;
}
return matrix.multiplyVec4(this, dest);
}
static mix(vector, vector2, time, dest) {
if (!dest) {
dest = new vec4();
}
dest.x = vector.x + time * (vector2.x - vector.x);
dest.y = vector.y + time * (vector2.y - vector.y);
dest.z = vector.z + time * (vector2.z - vector.z);
dest.w = vector.w + time * (vector2.w - vector.w);
return dest;
}
static sum(vector, vector2, dest) {
if (!dest) {
dest = new vec4();
}
dest.x = vector.x + vector2.x;
dest.y = vector.y + vector2.y;
dest.z = vector.z + vector2.z;
dest.w = vector.w + vector2.w;
return dest;
}
static difference(vector, vector2, dest) {
if (!dest) {
dest = new vec4();
}
dest.x = vector.x - vector2.x;
dest.y = vector.y - vector2.y;
dest.z = vector.z - vector2.z;
dest.w = vector.w - vector2.w;
return dest;
}
static product(vector, vector2, dest) {
if (!dest) {
dest = new vec4();
}
dest.x = vector.x * vector2.x;
dest.y = vector.y * vector2.y;
dest.z = vector.z * vector2.z;
dest.w = vector.w * vector2.w;
return dest;
}
static quotient(vector, vector2, dest) {
if (!dest) {
dest = new vec4();
}
dest.x = vector.x / vector2.x;
dest.y = vector.y / vector2.y;
dest.z = vector.z / vector2.z;
dest.w = vector.w / vector2.w;
return dest;
}
}
vec4.zero = new vec4([0, 0, 0, 1]);
vec4.one = new vec4([1, 1, 1, 1]);