/*
* This file is part of AUX.
*
* AUX is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* AUX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General
* Public License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
import { defineChildWidget } from '../child_widget.js';
import { LevelMeter } from './levelmeter.js';
import { Label } from './label.js';
import { Container } from './container.js';
import { addClass, removeClass, toggleClass } from '../utils/dom.js';
import { objectSub } from '../utils/object.js';
import { defineRender } from '../renderer.js';
const mapped_options = {
labels: 'label',
layout: 'layout',
};
function extractChildOptions(O, i) {
const o = {};
for (const _key in mapped_options) {
if (!Object.prototype.hasOwnProperty.call(O, _key)) continue;
const ckey = mapped_options[_key];
const value = O[_key];
const _type = LevelMeter.getOptionType(_key) || '';
if (Array.isArray(value) && _type.search('array') === -1) {
if (i < value.length) o[ckey] = value[i];
} else if (value !== null) {
o[ckey] = value;
}
}
return o;
}
function addMeter() {
const opt = extractChildOptions(this.options, this.meters.length);
const m = new LevelMeter(opt);
this.meters.push(m);
this.appendChild(m);
}
function removeMeter() {
/* meter can be int or meter instance */
const meter = this.meters.pop();
this.removeChild(meter);
meter.element.remove();
}
function mapChildOptionSimple(value, key) {
const M = this.meters;
for (let i = 0; i < M.length; i++) M[i].set(mapped_options[key], value);
}
function mapChildOption(value, key) {
const M = this.meters;
if (Array.isArray(value)) {
for (let i = 0; i < M.length && i < value.length; i++)
M[i].set(mapped_options[key], value[i]);
} else {
for (let i = 0; i < M.length; i++) M[i].set(key, value);
}
}
const multimeterOptionTypes = {
count: 'int',
label: 'boolean|string',
labels: 'array|string',
layout: 'string',
show_scale: 'boolean',
};
const multimeterOptionDefaults = {
count: 2,
label: false,
labels: null,
layout: 'left',
show_scale: true,
presets: {
mono: { count: 1, labels: ['C'] },
stereo: { count: 2, labels: ['L', 'R'] },
'2.1': { count: 3, labels: ['L', 'R', 'LF'] },
'3': { count: 3, labels: ['L', 'C', 'R'] },
'3.1': { count: 4, labels: ['L', 'C', 'R', 'LF'] },
'4': { count: 4, labels: ['L', 'R', "L'", "R'"] },
'4.1': { count: 5, labels: ['L', 'R', "L'", "R'", 'LF'] },
'5': { count: 5, labels: ['L', 'C', 'R', "L'", 'R'] },
'5.1': { count: 6, labels: ['L', 'C', 'R', "L'", "R'", 'LF'] },
'7.1': {
count: 6,
labels: ['L', 'C', 'R', "L'", "L''", "R''", "R'", 'LF'],
},
dolby_digital_1_0: { count: 1, labels: ['C'] },
dolby_digital_2_0: { count: 2, labels: ['L', 'R'] },
dolby_digital_3_0: { count: 3, labels: ['L', 'R', 'C'] },
dolby_digital_2_1: { count: 3, labels: ['L', 'R', "C'"] },
'dolby_digital_2_1.1': { count: 4, labels: ['L', 'R', "C'", 'LF'] },
dolby_digital_3_1: { count: 4, labels: ['L', 'R', 'C', "C'"] },
'dolby_digital_3_1.1': { count: 5, labels: ['L', 'R', 'C', "C'", 'LF'] },
dolby_digital_2_2: { count: 4, labels: ['L', 'R', "L'", "R'"] },
'dolby_digital_2_2.1': { count: 5, labels: ['L', 'R', "L'", "R'", 'LF'] },
dolby_digital_3_2: { count: 5, labels: ['L', 'R', 'C', "L'", "R'"] },
'dolby_digital_3_2.1': {
count: 6,
labels: ['L', 'R', 'C', "L'", "R'", 'LF'],
},
dolby_digital_ex: {
count: 7,
labels: ['L', 'R', 'C', "L'", "R'", "C'", 'LF'],
},
dolby_stereo: { count: 4, labels: ['L', 'R', 'C', "C'"] },
dolby_digital: { count: 4, labels: ['L', 'R', 'C', "C'"] },
dolby_pro_logic: { count: 4, labels: ['L', 'R', 'C', "C'"] },
dolby_pro_logic_2: {
count: 6,
labels: ['L', 'R', 'C', "L'", "R'", 'LF'],
},
dolby_pro_logic_2x: {
count: 8,
labels: ['L', 'R', 'C', "L'", "L''", "R''", "R'", 'LF'],
},
dolby_e_mono: {
count: 8,
labels: ['1', '2', '3', '4', '5', '6', '7', '8'],
},
dolby_e_stereo: {
count: 8,
labels: ['1L', '1R', '2L', '2R', '3L', '3R', '4L', '4R'],
},
'dolby_e_5.1_stereo': {
count: 8,
labels: ['1L', '1R', '1C', "1L'", "1R'", '1LF', '2L', '2R'],
},
},
};
const multimeterStaticEvents = {
set_labels: mapChildOption,
set_layout: mapChildOption,
};
const levelmeterOwnOptions = objectSub(
LevelMeter.getOptionTypes(),
Container.getOptionTypes()
);
for (const key in levelmeterOwnOptions) {
if (!Object.prototype.hasOwnProperty.call(levelmeterOwnOptions, key))
continue;
if (Object.prototype.hasOwnProperty.call(multimeterOptionTypes, key))
continue;
const type = levelmeterOwnOptions[key];
if (type.search('array') !== -1) {
multimeterOptionTypes[key] = type;
mapped_options[key] = key;
multimeterStaticEvents['set_' + key] = mapChildOptionSimple;
} else {
multimeterOptionTypes[key] = 'array|' + type;
mapped_options[key] = key;
multimeterStaticEvents['set_' + key] = mapChildOption;
}
}
/**
* MultiMeter is a collection of {@link LevelMeter}s to show levels of channels
* containing multiple audio streams. It offers all options of {@link LevelMeter} and
* {@link Meter} which are passed to all instantiated level meters.
*
* Available presets:
*
* <ul>
* <li>mono</li>
* <li>2.1</li>
* <li>3</li>
* <li>3.1</li>
* <li>4</li>
* <li>4.1</li>
* <li>5</li>
* <li>5.1</li>
* <li>7.1</li>
* <li>dolby_digital_1_0</li>
* <li>dolby_digital_2_0</li>
* <li>dolby_digital_3_0</li>
* <li>dolby_digital_2_1</li>
* <li>dolby_digital_2_1.1</li>
* <li>dolby_digital_3_1</li>
* <li>dolby_digital_3_1.1</li>
* <li>dolby_digital_2_2</li>
* <li>dolby_digital_2_2.1</li>
* <li>dolby_digital_3_2</li>
* <li>dolby_digital_3_2.1</li>
* <li>dolby_digital_ex</li>
* <li>dolby_stereo</li>
* <li>dolby_digital</li>
* <li>dolby_pro_logic</li>
* <li>dolby_pro_logic_2</li>
* <li>dolby_pro_logic_2x</li>
* <li>dolby_e_mono</li>
* <li>dolby_e_stereo</li>
* <li>dolby_e_5.1_stereo</li>
* </ul>
*
* @class MultiMeter
*
* @extends Container
*
* @param {Object} [options={ }] - An object containing initial options.
*
* @property {Number} [options.count=2] - The number of level meters.
* @property {String} [options.label=""] - The label of the multi meter. Set to `false` to hide the label from the DOM.
* @property {Array<String>} [options.labels=["L", "R"]] - An Array containing labels for the level meters. Their order is the same as the meters.
* @property {Array<Number>} [options.values=[]] - An Array containing values for the level meters. Their order is the same as the meters.
* @property {Array<Number>} [options.value_labels=[]] - An Array containing label values for the level meters. Their order is the same as the meters.
* @property {Array<Boolean>} [options.clips=[]] - An Array containing clippings for the level meters. Their order is the same as the meters.
* @property {Array<Number>} [options.tops=[]] - An Array containing values for top for the level meters. Their order is the same as the meters.
* @property {Array<Number>} [options.bottoms=[]] - An Array containing values for bottom for the level meters. Their order is the same as the meters.
*/
/* TODO: The following is not ideal cause we need to maintain it according to
LevelMeters and Meter options. */
export class MultiMeter extends Container {
static get _options() {
return multimeterOptionTypes;
}
static get options() {
return [multimeterOptionDefaults, LevelMeter.getDefaultOptions()];
}
static get static_events() {
return multimeterStaticEvents;
}
static get renderers() {
return [
defineRender('show_value', function (show_value) {
toggleClass(this.element, 'aux-has-values', show_value !== false);
}),
defineRender('labels', function (labels) {
toggleClass(this.element, 'aux-has-labels', labels !== false);
}),
defineRender('count', function (count) {
const E = this.element;
const prevCount = this.meters.length;
if (prevCount === count) return;
if (count > prevCount) {
for (let i = 0; i < count - prevCount; i++) addMeter.call(this);
} else {
for (let i = 0; i < prevCount - count; i++) removeMeter.call(this);
}
removeClass(E, 'aux-count-' + prevCount);
addClass(E, 'aux-count-' + count);
}),
defineRender('layout', function (layout) {
const E = this.element;
removeClass(
E,
'aux-vertical',
'aux-horizontal',
'aux-left',
'aux-right',
'aux-top',
'aux-bottom'
);
switch (layout) {
case 'left':
addClass(E, 'aux-vertical', 'aux-left');
break;
case 'right':
addClass(E, 'aux-vertical', 'aux-right');
break;
case 'top':
addClass(E, 'aux-horizontal', 'aux-top');
break;
case 'bottom':
addClass(E, 'aux-horizontal', 'aux-bottom');
break;
default:
throw new Error('unsupported layout');
}
}),
defineRender(['count', 'layout', 'show_scale'], function (
count,
layout,
show_scale
) {
switch (layout) {
case 'top':
case 'left':
this.meters.forEach((meter, i, meters) =>
meter.set('show_scale', show_scale && i + 1 === meters.length)
);
break;
case 'bottom':
case 'right':
this.meters.forEach((meter, i) =>
meter.set('show_scale', show_scale && i === 0)
);
break;
}
}),
];
}
initialize(options) {
super.initialize(options, true);
/**
* @member {HTMLDivElement} MultiMeter#element - The main DIV container.
* Has class <code>.aux-multimeter</code>.
*/
this.meters = [];
}
draw(O, element) {
addClass(element, 'aux-multimeter');
super.draw(O, element);
}
destroy() {
this.meters.map((meter) => {
meter.element.remove();
meter.destroy();
});
super.destroy();
}
}
/**
* @member {HTMLDivElement} MultiMeter#label - The {@link Label} widget displaying the meters title.
*/
defineChildWidget(MultiMeter, 'label', {
create: Label,
show: false,
option: 'label',
default_options: { class: 'aux-label' },
map_options: { label: 'label' },
toggle_class: true,
});