/*
 * Copyright (C) 2016-2020 Kano Computing Ltd.
 * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var SpeakerPart_1;
import { Part } from '../../part.js';
import { part, property, component } from '../../decorators.js';
import { samples } from './data.js';
import { SamplePlayer } from '../../../music/sample-player.js';
import { PartComponent } from '../../component.js';
import Output from '../../../output/output.js';
import { join } from '../../../util/path.js';
import { transformLegacySpeaker } from './legacy.js';
const bufferCache = new Map();
class SpeakerComponent extends PartComponent {
    constructor() {
        super(...arguments);
        this.playbackRate = 1;
        this.volume = 0.4;
    }
}
__decorate([
    property({ type: Number, value: 1 })
], SpeakerComponent.prototype, "playbackRate", void 0);
__decorate([
    property({ type: Number, value: 0.4 })
], SpeakerComponent.prototype, "volume", void 0);
const SAMPLE_URL_PREFIX_KEY = 'speaker:base-url';
const legacyValueCheck = (value, legacyIdMap) => {
    const newId = legacyIdMap.get(value);
    if (newId) {
        return newId;
    }
    return value;
};
let SpeakerPart = SpeakerPart_1 = class SpeakerPart extends Part {
    constructor() {
        super();
        this.players = [];
        this.samplesMap = new Map();
        this._samples = [];
        this._defaultSample = '';
        this.legacyIdMap = new Map();
        this.constructor.items.forEach((set) => {
            const sampleSet = { id: set.id, label: set.label, resources: [] };
            this._samples.push(sampleSet);
            set.resources.forEach((sample) => {
                sampleSet.resources.push(Object.assign(sample, {}));
                // Build a map of id => sample data to access those swiftly
                this.samplesMap.set(sample.id, sample);
            });
        });
        this._defaultSample = this.constructor.defaultSample;
        this.core = this._components.get('core');
        this.core.onDidInvalidate(() => {
            this.render();
        }, this, this.subscriptions);
    }
    static get defaultSample() { return 'claves'; }
    static get items() { return samples; }
    static get legacyIdMap() { return new Map(); }
    static resolve(id) {
        const prefix = Output.config.get(SAMPLE_URL_PREFIX_KEY, '/assets/audio/samples/');
        return join(prefix, id);
    }
    static transformLegacy(app) {
        transformLegacySpeaker(app);
    }
    onInstall(context) {
        this.ctx = context.audio.context;
        this.destination = context.audio.destination;
    }
    loadSample(src) {
        return fetch(src)
            .then(r => r.arrayBuffer())
            .then(r => this.ctx.decodeAudioData(r));
    }
    getSample(src) {
        const srcRes = legacyValueCheck(src, this.legacyIdMap);
        if (!bufferCache.has(srcRes)) {
            return this.loadSample(srcRes)
                .then((buffer) => {
                bufferCache.set(srcRes, buffer);
                return buffer;
            });
        }
        return Promise.resolve(bufferCache.get(srcRes));
    }
    onStop() {
        super.onStop();
        this.stop();
    }
    render() {
        if (!this.core.invalidated) {
            return;
        }
        this.players.forEach((player) => {
            player.source.playbackRate.value = this.core.playbackRate;
            player.volume = this.core.volume;
        });
        this.core.apply();
    }
    _play(id, loop = false, time) {
        const sample = this.samplesMap.get(id);
        if (!sample) {
            return;
        }
        this.getSample(SpeakerPart_1.resolve(sample.src))
            .then((buffer) => {
            const player = new SamplePlayer(this.ctx, buffer, this.destination, { loop });
            player.play(time);
            if (!player.source) {
                return;
            }
            player.source.playbackRate.value = this.core.playbackRate;
            player.volume = this.core.volume;
            // Remove play when it finishes
            player.onDidEnd(() => {
                const idx = this.players.indexOf(player);
                if (idx !== -1) {
                    this.players.splice(idx, 1);
                }
            });
            this.players.push(player);
        });
    }
    play(id, time) {
        this._play(id, false, time);
    }
    loop(id) {
        this._play(id, true);
    }
    stop() {
        // using a decrementing loop to ensure array is fully traversed, even when spliced
        for (var i = this.players.length; i > 0; i -= 1) {
            try {
                this.players[i - 1].stop();
            }
            catch (e) { }
        }
        this.players.length = 0;
    }
    randomFrom(id) {
        const set = this._samples.find(set => set.id === id);
        if (!set) {
            return this._defaultSample;
        }
        const sample = set.resources[Math.floor(Math.random() * set.resources.length)];
        return sample.id;
    }
    setLegacyIdMap(map) {
        this.legacyIdMap = map;
    }
    getLegacyIdMap() {
        return this.legacyIdMap;
    }
    set pitch(r) {
        this.core.playbackRate = Math.min(Math.max(r, 0), 200) / 100;
        this.core.invalidate();
    }
    get pitch() {
        return this.core.playbackRate * 100;
    }
    set volume(v) {
        // the maximum value is limited to an ear-friendly sound pressure level
        this.core.volume = Math.min(Math.max(v, 0), 100) * 0.004;
        this.core.invalidate();
    }
    get volume() {
        return this.core.volume / 0.004;
    }
};
__decorate([
    component(SpeakerComponent)
], SpeakerPart.prototype, "core", void 0);
SpeakerPart = SpeakerPart_1 = __decorate([
    part('speaker')
], SpeakerPart);
export { SpeakerPart };
//# sourceMappingURL=speaker.js.map