/* MixingMatrix.cpp
 *
 * Copyright (C) 2010-2017 David Weenink
 *
 * This code 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 2 of the License, or (at
 * your option) any later version.
 *
 * This code 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 work. If not, see <http://www.gnu.org/licenses/>.
 */

#include "Interpreter.h"
#include "MixingMatrix.h"

Thing_implement (MixingMatrix, TableOfReal, 0);

autoMixingMatrix MixingMatrix_create (integer numberOfOutputChannels, integer numberOfInputChannels) {
	try {
		autoMixingMatrix me = Thing_new (MixingMatrix);
		TableOfReal_init (me.get(), numberOfOutputChannels, numberOfInputChannels);
		return me;
	} catch (MelderError) {
		Melder_throw (U"MixingMatrix not created.");
	}
}

autoMixingMatrix MixingMatrix_createSimple (integer numberOfOutputChannels, integer numberOfInputChannels, conststring32 elements) {
	try {
		integer inum = 1, ntokens = Melder_countTokens (elements);
		if (ntokens == 0) {
			Melder_throw (U"No matrix elements.");
		}
		integer numberOfCells = numberOfInputChannels * numberOfOutputChannels;

		autoMixingMatrix me = MixingMatrix_create (numberOfOutputChannels, numberOfInputChannels);

		/*
			Construct the full matrix from the elements
		*/
		double number;
		for (char32 *token = Melder_firstToken (elements); token && inum <= ntokens; token = Melder_nextToken (), inum ++) {
			integer irow = (inum - 1) / numberOfInputChannels + 1;
			integer icol = (inum - 1) % numberOfInputChannels + 1;
			Interpreter_numericExpression (0, token, & number);

			my data [irow] [icol] = number;
		}
		if (ntokens < numberOfCells) {
			for (integer i = inum; i <= numberOfCells; i ++) {
				integer irow = (inum - 1) / numberOfInputChannels + 1;
				integer icol = (inum - 1) % numberOfInputChannels + 1;
				my data [irow] [icol] = number; // repeat the last number given!
			}
		}
		return me;
	} catch (MelderError) {
		Melder_throw (U"MixingMatrix not created.");
	}
}

void MixingMatrix_setRandomGauss (MixingMatrix me, double mean, double stdev) {
	for (integer i = 1; i <= my numberOfRows; i++) {
		for (integer j = 1; j <= my numberOfColumns; j++) {
			my data[i][j] = NUMrandomGauss (mean, stdev);
		}
	}
}

void MixingMatrix_multiplyInputChannel (MixingMatrix me, integer inputChannel, double value) {
	if (inputChannel >= 1 && inputChannel <= my numberOfColumns) {
		for (integer i = 1; i <= my numberOfRows; i ++) {
			my data [i] [inputChannel] *= value;
		}
	}
}

// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/channelInterpretation
void MixingMatrix_setStandardChannelInterpretation (MixingMatrix me) {
	for (integer i = 1; i <= my numberOfRows; i++) {
		for (integer j = 1; j <= my numberOfColumns; j++) {
			my data [i][j] = 0;
		}
	}
	bool dimensionsCovered = true;
	if (my numberOfColumns == 1) { // mono input
		if (my numberOfRows == 2) { // up-mix to stereo
			// The M input channel is used for both output channels (L and R).
			//		output.L = input.M
			//		output.R = input.M
			my data [1][1] = my data [2][1] = 1.0;
		} else if (my numberOfRows == 4) { // up-mix to to quad
			// The M input channel is used for non-surround output channels (L and R).
			// Surround output channels (SL and SR) are silent.
			//		output.L = input.M
			//		output.R = input.M
			//		output.SL = 0
			//	output.SR = 0 
			my data [1][1] = my data [2][1] = 1.0;
		} else if (my numberOfRows == 6) { // up-mix to to 5.1
			// The M input channel is used for the center output channel (C). 
			// All the others (L, R, LFE, SL, and SR) are silent.
			//		output.L = 0
			//		output.R = 0
			//		output.C = input.M
			//		output.LFE = 0
			//		output.SL = 0
			//		output.SR = 0
			my data [3][1] = 1.0;
		} else { 
			dimensionsCovered = false;
		}
	} else if (my numberOfColumns == 2) { // stereo input
		if (my numberOfRows == 1) { // down-mix to mono
			// Both input channels (L and R) are equally combined to produce the unique output channel (M).
			//		output.M = 0.5 * (input.L + input.R)
			my data [1][1] = my data [1][2] = 0.5;
		} else if (my numberOfRows == 2) { // to stereo
			// 	output.L = input.L
			// 	output.R = input.R
			my data [1][1] = my data [2][2] = 1.0;
		} else if (my numberOfRows == 4) { // up-mix to quad
			// The L and R input channels are used for their non-surround respective output channels (L and R). 
			// Surround output channels (SL and SR) are silent.
			//		output.L = input.L
			//		output.R = input.R
			//		output.SL = 0
			//		output.SR = 0
			my data [1][1] = my data [2][2] = 1.0;
		} else if (my numberOfRows == 6) { // up-mix to 5.1
			// The L and R input channels are used for their non-surround respective output channels (L and R). 
			// Surround output channels (SL and SR), as well as the center (C) and subwoofer (LFE) channels, are left silent.
			//		output.L = input.L
			//		output.R = input.R
			//		output.C = 0
			//		output.LFE = 0
			//		output.SL = 0
			//		output.SR = 0
			my data [1][1] = my data [2][2] = 1.0;
		} else { // up-mix 
			dimensionsCovered = false;
		}
	} else if (my numberOfColumns == 4) { // quad input
		if (my numberOfRows == 1) { // down-mix to mono
			// All four input channels (L, R, SL, and SR) are equally combined to produce the unique output channel (M).
			//		output.M = 0.25 * (input.L + input.R + input.SL + input.SR)
			my data [1][1] = my data [1][2] = my data [1][3] = my data [1][4] = 0.25;
		} else if (my numberOfRows == 2) { // down-mix to stereo
			// Both left input channels (L and SL) are equally combined to produce the unique left output channel (L). 
			// And similarly, both right input channels (R and SR) are equally combined to produce the unique right output channel (R).
			//			output.L = 0.5 * (input.L + input.SL)
			//			output.R = 0.5 * (input.R + input.SR)
			my data [1][1] = my data [1][3] = 0.5;
			my data [1][2] = my data [1][4] = 0.5;
		} else if (my numberOfRows == 6) { // up-mix to 5.1
			// The L, R, SL, and SR input channels are used for their respective output channels (L and R). 
			// Center (C) and subwoofer (LFE) channels are left silent.
			//		output.L = input.L
			//		output.R = input.R
			//		output.C = 0
			//		output.LFE = 0
			//		output.SL = input.SL
			//		output.SR = input.SR
			my data [1][1] = my data [2][2] = my data [5][3] = my data [6][4] = 1.0;
		} else {
			dimensionsCovered = false;
		}
	} else if (my numberOfColumns == 6) { // 5.1 input)
		if (my numberOfRows == 1) { // down-mix to mono
			// The left (L and SL), right (R and SR) and central channels are all mixed together. 
			// The surround channels are slightly attenuated and the regular lateral channels are power-compensated to make them count as a single channel by multiplying by √2/2. 
			// The subwoofer (LFE) channel is lost.
			//		output.M = 0.7071 * (input.L + input.R) + input.C + 0.5 * (input.SL + input.SR)
			my data [1][3] = 1.0;
			my data [1][1] = my data [1][2] = 0.5; // NUMsqrt1_2 is not safe if the channels are equal;
			my data [1][5] = my data [1][6] = 0.5;
		} else if (my numberOfRows == 2)  { // down-mix to stereo
			// The central channel (C) is summed with each lateral surround channel (SL or SR) and mixed to each lateral channel. 
			// As it is mixed down to two channels, it is mixed at a lower power: in each case it is multiplied by √2/2. 
			// The subwoofer (LFE) channel is lost.
			//		output.L = input.L + 0.7071 * (input.C + input.SL)
			//		output.R = input.R + 0.7071 * (input.C + input.SR)
			my data [1][1] = my data [2][2] = 1.0;
			my data [1][3] = my data [1][5] = 0.5; // NUMsqrt1_2;
			my data [2][4] = my data [2][6] = 0.5; // NUMsqrt1_2;
		} else if (my numberOfRows == 4)  { // down-mix to quad
			// The central (C) is mixed with the lateral non-surround channels (L and R). 
			// As it is mixed down to two channels, it is mixed at a lower power: in each case it is multiplied by √2/2. 
			// The surround channels are passed unchanged. The subwoofer (LFE) channel is lost.
			//		output.L = input.L + 0.7071 * input.C
			//		output.R = input.R + 0.7071 * input.C
			//		output.SL = input.SL
			//		output.SR = input.SR
			my data [1][1] = my data [2][2] = my data [3][5] = my data [4][6] = 1.0;
			my data [1][3] = my data [2][3] = NUMsqrt1_2;
		} else {
			dimensionsCovered = false;
		}
	} else {
		dimensionsCovered = false;
	}
	if (! dimensionsCovered) {
		// Fill each output channel with its input counterpart, that is the input channel with the same index. 
		// Channels with no corresponding input channels are left silent.
		integer lowerDimension = my numberOfRows < my numberOfColumns ? my numberOfRows: my numberOfColumns;
		for (integer i = 1; i <= lowerDimension; i++) {
			my data [i][i] = 1.0;
		}
	}
}

void MixingMatrix_muteAndActivateChannels (MixingMatrix me, bool *muteChannels) {
	integer numberOfMuteChannels = 0;
	for (integer icol = 1; icol <= my numberOfColumns; icol++) {
		if (muteChannels [icol]) {
			numberOfMuteChannels ++;
		}
	}
	// Set all mute channels to 0 and all other channels to 1. To prevent overflow scale by the number of channels that are on.
	double coefficient = my numberOfColumns > numberOfMuteChannels ? 1.0 / (my numberOfColumns - numberOfMuteChannels) : 0.0;
	for (integer icol = 1; icol <= my numberOfColumns; icol ++) {
		double channelScaling = muteChannels [icol] ? 0.0 : coefficient;
		for (integer irow = 1; irow <= my numberOfRows; irow ++) {
			my data [irow][icol] = channelScaling;
		}
	}
}

/* End of file MixingMatrix.cpp */
