001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030/**
031 *
032 */
033package org.openimaj.audio.conversion;
034
035import org.openimaj.audio.AudioFormat;
036import org.openimaj.audio.AudioStream;
037import org.openimaj.audio.SampleChunk;
038import org.openimaj.audio.processor.AudioProcessor;
039
040/**
041 *      A class that will work out which processors to instantiate to provide
042 *      with a complete conversion from one audio format to another.
043 *
044 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
045 *  @created 13 May 2013
046 */
047public class AudioConverter extends AudioProcessor
048{
049        /** The input format */
050        private AudioFormat inputFormat = null;
051
052        /** The calculated audio processor */
053        private AudioProcessor processor = null;
054
055        /**
056         *      Chainable constructor that takes the stream to chain and the
057         *      output format to convert the stream to.
058         *      @param stream The stream to chain to
059         *      @param output The required output format
060         */
061        public AudioConverter( final AudioStream stream, final AudioFormat output )
062        {
063                super( stream );
064                this.inputFormat = stream.getFormat().clone();
065                this.setFormat( output.clone() );
066                this.processor = AudioConverter.calculateProcess( this.inputFormat, output );
067        }
068
069        /**
070         *      Constructor that takes the input and output formats.
071         *      @param input The input format
072         *      @param output The output format
073         */
074        public AudioConverter( final AudioFormat input, final AudioFormat output )
075        {
076                this.inputFormat = input.clone();
077                this.setFormat( output.clone() );
078                this.processor = AudioConverter.calculateProcess( input, output );
079        }
080
081        /**
082         *      Calculates the chain of processors that will convert from one format to the
083         *      other and will return the first in the chain. If there is no processing to be
084         *      done the method will return null.
085         *
086         *      @param input The input format
087         *      @param output The output format.
088         *      @return The first processor in the stream.
089         */
090        public static AudioProcessor calculateProcess( final AudioFormat input, final AudioFormat output )
091        {
092                // If the input and output formats are the same, then there's
093                // no processing to do, so we return null.
094                if( input.equals( output ) )
095                        return null;
096
097                AudioProcessor ap = null;
098
099                // Note that we construct this chain back-to-front. So, if
100                // all the processors will be used, then the channel processor,
101                // then sample rate, then bit depth processor.  This should be
102                // the most efficient order.
103
104                // Check if the endian is the same.
105                if( input.isBigEndian() != output.isBigEndian() )
106                        throw new IllegalArgumentException( "Cannot convert "+input+" to "+output+
107                                        ". There is no endian conversion implemented yet." );
108
109                // Check if the signedness is the same
110                if( input.isSigned() != output.isSigned() )
111                        throw new IllegalArgumentException( "Cannot convert "+input+" to "+output+
112                                        ". There is no sign conversion implemented yet." );
113
114                // Check if the sample rates are different
115                if( input.getSampleRateKHz() != output.getSampleRateKHz() )
116                        ap = AudioConverter.getProcessor( ap, new SampleRateConverter(
117                                        SampleRateConverter.SampleRateConversionAlgorithm.LINEAR_INTERPOLATION,
118                                                AudioConverter.getFormatSR( ap, output ) ) );
119
120                // Check if the bits are different
121                if( input.getNBits() != output.getNBits() )
122                        ap = AudioConverter.getProcessor( ap, new BitDepthConverter(
123                                        BitDepthConverter.BitDepthConversionAlgorithm.NEAREST,
124                                                AudioConverter.getFormatBits( ap, output ) ) );
125
126                // Check if the channels are different
127                if( input.getNumChannels() != output.getNumChannels() )
128                {
129                        if( output.getNumChannels() == 1 )
130                                        ap = AudioConverter.getProcessor( ap, new MultichannelToMonoProcessor() );
131                        else    throw new IllegalArgumentException( "Cannot convert "+input+" to "+output+
132                                                                ". Unable to find an appropriate channel converter." );
133                }
134
135                return ap;
136        }
137
138        /**
139         *      Updates the number of bits in the source processor's format to match the output format.
140         *
141         *      @param ap The source processor
142         *      @param output The output format
143         *      @return The fixed source format
144         */
145        private static AudioFormat getFormatBits( final AudioProcessor ap , final AudioFormat output )
146        {
147                if( ap == null ) return output;
148                final AudioFormat f = ap.getFormat().clone();
149                f.setNBits( output.getNBits() );
150                return f;
151        }
152
153        /**
154         *      Updates the sample rate in the source processor's format to match the output format.
155         *
156         *      @param ap The source processor
157         *      @param output The output format
158         *      @return The fixed source format
159         */
160        private static AudioFormat getFormatSR( final AudioProcessor ap, final AudioFormat output )
161        {
162                if( ap == null ) return output;
163                final AudioFormat f = ap.getFormat().clone();
164                f.setSampleRateKHz( output.getSampleRateKHz() );
165                return f;
166        }
167
168        /**
169         *      Gets the top in the chain of processors (when constructing the chain in reverse order).
170         *      That is, the second processor will always be returned from this method. If the first
171         *      processor is not null, it will be made the source stream for the second processor.
172         *
173         *      @param ap The source processor
174         *      @param ap2 The destination processor
175         *      @return The destination processor
176         */
177        private static AudioProcessor getProcessor( final AudioProcessor ap, final AudioProcessor ap2 )
178        {
179                if( ap == null )
180                        return ap2;
181                ap2.setUnderlyingStream( ap );
182                return ap2;
183        }
184
185        @Override
186        public SampleChunk process( final SampleChunk sample ) throws Exception
187        {
188                return this.processor.process( sample );
189        }
190
191        /**
192         *      Same as {@link #getFormat()} for this class - returns the output format.
193         *      @return The output format.
194         */
195        public AudioFormat getOutputFormat()
196        {
197                return this.getFormat();
198        }
199}