private static float[] GetNormalizedMonoMix(float[][] samples) {
/*
* This mixer tries to find silent channels and discards them.
* It then performs a mix to mono for all remaining channels
* and tries to detect destructive interference (in which case
* the first non-silent channel is returned). In all other cases,
* volume in the mono mix is normalized to the average volume
* in all non-silent channels. If necessary, the volume is
* further normalized to prevent overflow. This also prevents
* constructive interference.
* */
// --- determine the volume per channel and the total volume ---
float[] channelVolume = new float[samples.Length];
float totalVolume = 0.0f;
for (int i = 0; i < samples.Length; i++) {
for (int j = 0; j < samples[i].Length; j++) {
channelVolume[i] += Math.Abs(samples[i][j]);
}
channelVolume[i] /= samples[i].Length;
totalVolume += channelVolume[i];
}
totalVolume /= samples.Length;
// --- discard all channels that are below
// a certain threshold of the total volume ---
const float silentThreshold = 0.05f;
float[][] remainingSamples = new float[samples.Length][];
int remainingSamplesUsed = 0;
for (int i = 0; i < samples.Length; i++) {
if (!(channelVolume[i] > silentThreshold*totalVolume)) continue;
channelVolume[remainingSamplesUsed] = channelVolume[i];
remainingSamples[remainingSamplesUsed] = samples[i];
remainingSamplesUsed++;
}
switch (remainingSamplesUsed)
{
case 1:
return remainingSamples[0];
case 0:
remainingSamples = samples;
remainingSamplesUsed = samples.Length;
break;
default:
totalVolume = 0.0f;
for (int i = 0; i < samples.Length; i++) {
totalVolume += channelVolume[i];
}
totalVolume /= remainingSamplesUsed;
break;
}
// --- produce a mono mix from all remaining channels ---
float[] mix = new float[remainingSamples[0].Length];
float mixVolume = 0.0f;
for (int j = 0; j < remainingSamples[0].Length; j++) {
for (int i = 0; i < remainingSamplesUsed; i++) {
mix[j] += remainingSamples[i][j];
}
mix[j] /= remainingSamplesUsed;
mixVolume += Math.Abs(mix[j]);
}
mixVolume /= remainingSamples[0].Length;
// --- if the volume in the mono mix is below
// a certain threshold of the total volume,
// assume destructive interference and return
// the first non-silent channel ---
const float destructiveInterferenceThreshold = 0.05f;
if (mixVolume < destructiveInterferenceThreshold * totalVolume) {
return remainingSamples[0];
}
// --- normalize the volume in the mono mix so that
// it corresponds to the average total volume ---
float maximum = 0.0f;
for (int j = 0; j < mix.Length; j++) {
mix[j] *= totalVolume / mixVolume;
float value = Math.Abs(mix[j]);
if (value > maximum) maximum = value;
}
// --- if the maximum value now created exceeds the
// permissible range, normalize the mono mix further ---
if (maximum > 1.0f) {
for (int j = 0; j < mix.Length; j++) {
mix[j] /= maximum;
}
}
return mix;
}