// 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.filterFreq = 24000;
        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();
        this.filter = this.context.getContext().createBiquadFilter();
        this.filter.type = "highshelf";
        this.filter.frequency.value = this.filterFreq;
        this.filter.gain.value = -60;
        // bind methods so we can add and removve them from event listeners
        this.stop = this.stop.bind(this);
    }
    getBuffer() {
        return this.buffer;
    }
    setBuffer(data) {
        this.buffer = data;
        if (this.playOnLoad) {
            this.play();
            this.playOnLoad = false;
        }
    }
    play(when, offset, duration) {
        if (!this.context)
            return;
        if (this.playing && this.node) {
            this.stop();
        }
        if (!this.buffer) {
            this.playOnLoad = true;
            this.playing = true;
            return;
        }
        if (!this.node) {
            this.node = this.context.createBufferSource();
            this.node.buffer = this.buffer;
            this.createConnections();
        }
        if (this.node) {
            this.node.playbackRate.value = this.playbackRate;
            // Have to do this, otherwise when we pass duration, the node will stop before it can loop. 
            if (duration) {
                this.node.start(when, offset, duration);
            }
            else {
                this.node.start(when, offset);
            }
            this.node.loop = this.looping;
            this.playing = true;
            if (this.sceneNode) {
                this.sceneNode.setPosition(this.position.x, this.position.y, this.position.z);
            }
            this.node.addEventListener('ended', this.stop);
        }
    }
    setPosition(x, y, z) {
        if (x === this.position.x && y === this.position.y && z === this.position.z)
            return;
        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.updateSpatialization();
                }
                this.node.connect(this.filter);
                this.filter.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);
    }
    isPlaying() {
        return this.playing;
    }
    setDistanceModel(distanceModel) {
        this.distanceModel = distanceModel;
        this.updateSpatialization();
    }
    setMaxDistance(distance) {
        this.maxDistance = this.maxDistance;
        this.updateSpatialization();
    }
    setRefDistance(ref) {
        this.refDistance = ref;
        this.updateSpatialization();
    }
    setRollOffFactor(factor) {
        this.rollOffFactor = factor;
        this.updateSpatialization();
    }
    updateSpatialization() {
        if (this.sceneNode) {
            if (this.distanceModel)
                this.sceneNode.distanceModel = this.distanceModel;
            if (this.refDistance)
                this.sceneNode.refDistance = this.refDistance;
            if (this.rollOffFactor)
                this.sceneNode.rolloffFactor = this.rollOffFactor;
            if (this.maxDistance)
                this.sceneNode.maxDistance = this.maxDistance;
        }
    }
    setFilterFrequency(value) {
        this.filterFreq = value;
        if (this.filter) {
            this.filter.frequency.value = this.filterFreq;
        }
    }
}
