/** * Copyright 2004-2006 DFKI GmbH. * All Rights Reserved. Use is subject to license terms. * * This file is part of MARY TTS. * * MARY TTS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package marytts.signalproc.process; import java.io.File; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import marytts.util.data.BufferedDoubleDataSource; import marytts.util.data.DoubleDataSource; import marytts.util.data.audio.AudioDoubleDataSource; import marytts.util.data.audio.AudioProcessor; import marytts.util.data.audio.DDSAudioInputStream; import marytts.util.math.MathUtils; /** * @author Marc Schröder * */ public class EnergyNormaliser implements AudioProcessor { protected double amplitudeFactor; protected double referencePower; /** * Adapt the amplitudes of a signal such that the energy changes by the given factor. * * @param energyFactor * energy factor */ public EnergyNormaliser(double energyFactor) { this.amplitudeFactor = Math.sqrt(energyFactor); this.referencePower = -1; } /** * Adapt the amplitudes of a signal such that the average power is the same as the one in the reference. * * @param reference * an audio input stream with the reference power. */ public EnergyNormaliser(AudioInputStream reference) { this.referencePower = determineAveragePower(reference); this.amplitudeFactor = -1; } /** * Adapt the amplitudes of a signal such that the average power is the same as the one in the reference. * * @param reference * an audio signal with the reference power. */ public EnergyNormaliser(DoubleDataSource reference) { this.referencePower = determineAveragePower(reference); this.amplitudeFactor = -1; } public double getAmplitudeFactor() { return amplitudeFactor; } public double getReferencePower() { return referencePower; } /** * For a given audio input stream, determine the average power. * * @param ais * audio input stream for which to determine the average power * @return a non-negative double representing the average power as energy per sample, i.e. the total energy divided by the * total duration. */ public static double determineAveragePower(AudioInputStream ais) { if (ais == null) throw new NullPointerException("Received null argument"); DoubleDataSource signal = new AudioDoubleDataSource(ais); return determineAveragePower(signal); } /** * For a given audio signal, determine the average power. * * @param signal * a double data source for which to determine the average power * @return a non-negative double representing the average power as energy per sample, i.e. the total energy divided by the * total duration. */ public static double determineAveragePower(DoubleDataSource signal) { if (signal == null) throw new NullPointerException("Received null argument"); double[] signalData = signal.getAllData(); return determineAveragePower(signalData); } /** * For a given audio signal and sampling rate, determine the average power. * * @param signal * audio signal for which to determine the average power * @return a non-negative double representing the average power as energy per sample, i.e. the total energy divided by the * total duration. */ public static double determineAveragePower(double[] signal) { if (signal == null) throw new NullPointerException("Received null argument"); if (signal.length == 0) return 0; double energy = MathUtils.sum(MathUtils.multiply(signal, signal)); if (energy == 0 || signal.length == 0) return 0; else return energy / signal.length; } public AudioInputStream apply(AudioInputStream ais) { AudioDoubleDataSource adds = new AudioDoubleDataSource(ais); return new DDSAudioInputStream(this.apply(adds), adds.getAudioFormat()); } public DoubleDataSource apply(DoubleDataSource signal) { final double factor; DoubleDataSource source; if (amplitudeFactor >= 0) { // Simple multiplication with amplitude factor factor = amplitudeFactor; source = signal; } else { // reference power -- need to compute average power first. assert referencePower >= 0; double[] signalData = signal.getAllData(); double power = determineAveragePower(signalData); if (power == 0) factor = 0; else factor = Math.sqrt(referencePower / power); source = new BufferedDoubleDataSource(signalData); } System.err.println("Applying amplitude factor: " + factor); return new BufferedDoubleDataSource(source, new InlineDataProcessor() { public void applyInline(double[] buf, int off, int len) { for (int i = off; i < off + len; i++) { buf[i] *= factor; } } }); } public static void main(String[] args) throws Exception { /* * AudioInputStream dummyAIS = AudioSystem.getAudioInputStream(new File(args[0])); double[] testSignal = new * double[16000]; for (int i=0; i<testSignal.length; i++) { testSignal[i] = * 10000*Math.sin(2*Math.PI*i*1000/testSignal.length); } //FunctionGraph graph = new FunctionGraph(0, 1, testSignal); * //graph.showInJFrame("testSignal", true, false); DDSAudioInputStream testAIS = new DDSAudioInputStream(new * BufferedDoubleDataSource(testSignal), dummyAIS.getFormat()); //AudioSystem.write(testAIS, AudioFileFormat.Type.WAVE, * new File("testSignal.wav")); //testAIS = new DDSAudioInputStream(new BufferedDoubleDataSource(testSignal), * dummyAIS.getFormat()); EnergyNormaliser normaliser = new EnergyNormaliser(2); AudioInputStream resultAIS = * normaliser.apply(testAIS); System.err.println("Constructed resultAIS"); double[] testResult = new * AudioDoubleDataSource(resultAIS).getAllData(); System.err.println("Got all data from resultAIS"); FunctionGraph * resultGraph = new FunctionGraph(0, 1, testResult); resultGraph.showInJFrame("testResult", true, false); */ double[] totalEnergies = new double[args.length]; double[] maxAmplitude = new double[args.length]; for (int i = 0; i < args.length; i++) { double[] signal = new AudioDoubleDataSource(AudioSystem.getAudioInputStream(new File(args[i]))).getAllData(); totalEnergies[i] = 0; for (int j = 0; j < signal.length; j++) { totalEnergies[i] += signal[j] * signal[j]; } maxAmplitude[i] = MathUtils.max(signal); System.err.println(args[i] + ": total energy = " + totalEnergies[i] + ", max amplitude = " + maxAmplitude[i]); } double meanEnergy = MathUtils.mean(totalEnergies); int indexMaxAmplitude = MathUtils.findGlobalPeakLocation(maxAmplitude); System.err.println("Mean energy: " + meanEnergy); System.err.println("Highest amplitude found in " + args[indexMaxAmplitude]); int MAX_AMPLITUDE = 32767; for (int i = 0; i < args.length; i++) { double energyFactor = meanEnergy / totalEnergies[i]; System.err.println(args[i] + ": applying factor " + energyFactor + " = " + meanEnergy + " / " + totalEnergies[i]); if (maxAmplitude[i] * Math.sqrt(energyFactor) > MAX_AMPLITUDE) { System.err.println("Warning: signal clipping in file " + args[i] + "_norm.wav"); } EnergyNormaliser norm = new EnergyNormaliser(energyFactor); File f = new File(args[i]); AudioInputStream ais = AudioSystem.getAudioInputStream(f); AudioInputStream result = norm.apply(ais); String outFileName = args[i].substring(0, args[i].lastIndexOf('.')) + "_norm.wav"; File outFile = new File(outFileName); AudioSystem.write(result, AudioFileFormat.Type.WAVE, outFile); } } }