1 /**
2 * Copyright (c) 2011, The University of Southampton and the individual contributors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * * Neither the name of the University of Southampton nor the names of its
16 * contributors may be used to endorse or promote products derived from this
17 * software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30 /**
31 *
32 */
33 package org.openimaj.audio.conversion;
34
35 import org.openimaj.audio.AudioFormat;
36 import org.openimaj.audio.AudioStream;
37 import org.openimaj.audio.SampleChunk;
38 import org.openimaj.audio.processor.AudioProcessor;
39 import org.openimaj.audio.samples.SampleBuffer;
40 import org.openimaj.audio.samples.SampleBufferFactory;
41 import org.openimaj.math.util.Interpolation;
42
43 /**
44 * A sample rate conversion audio processing class. There is an enumerator
45 * within the class that is publically available for determining the
46 * algorithm for sample rate conversion. This defaults to
47 * {@link SampleRateConversionAlgorithm#LINEAR_INTERPOLATION}.
48 * <p>
49 * To use the class, instantiate using the default constructor or the
50 * chainable constructor. Both of these constructors take the algorithm for
51 * sample rate conversion as well as the output format. The output format
52 * must have the same number of bits and same number of channels as the
53 * expected input otherwise the {@link #process(SampleChunk)} method
54 * will throw {@link IllegalArgumentException}. The input format for the samples
55 * is expected to be provided as part of the {@link SampleChunk}.
56 * <p>
57 * The class itself checks whether the output format and the input format
58 * are the same (in which case the sample does not need to be resampled).
59 * That means the algorithm implementation does not need to do this.
60 *
61 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
62 *
63 * @created 18 Jun 2012
64 */
65 public class SampleRateConverter extends AudioProcessor
66 {
67 /**
68 * An enumerator of the different sample rate conversion algorithms
69 * available in this sample rate converter.
70 *
71 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
72 *
73 * @created 18 Jun 2012
74 */
75 public enum SampleRateConversionAlgorithm
76 {
77 /**
78 * Performs linear interpolation between samples where the sample rate
79 * in the output format is greater than the sample rate of the input
80 * format. If the sample rate is less, then the nearest value
81 * of the input samples is used.
82 */
83 LINEAR_INTERPOLATION
84 {
85 @Override
86 public SampleChunk process( final SampleChunk s, final AudioFormat output)
87 {
88 final AudioFormat input = s.getFormat();
89
90 // Check to see if the input and output are the same
91 if( input.getSampleRateKHz() == output.getSampleRateKHz() )
92 return s;
93
94 // Work out the size of the output sample chunk
95 final double scalar = input.getSampleRateKHz() / output.getSampleRateKHz();
96 final SampleBuffer sbin = s.getSampleBuffer();
97 final double size = sbin.size() / scalar;
98
99 if( this.sbout == null || this.sbout.size() != (int)size )
100 {
101 this.sbout = SampleBufferFactory.createSampleBuffer(
102 output, (int)size );
103 this.sbout.setFormat( output );
104 }
105
106 // If the input format has a greater sample rate than the
107 // output format - down sampling (scalar > 1)
108 if( scalar > 1 )
109 {
110 for( int i = 0; i < this.sbout.size(); i++ )
111 this.sbout.set( i, sbin.get( (int)(i * scalar) ) );
112 return this.sbout.getSampleChunk();
113 }
114 // If the input format has a sample rate less than that
115 // of the output - up sampling (scalar < 1)
116 else
117 {
118 // Linear interpolate each sample value
119 for( int i = 0; i < this.sbout.size()-1; i++ )
120 {
121 final int inputSampleX = (int)(i * scalar);
122 this.sbout.set( i, Interpolation.lerp( (float)(i*scalar),
123 inputSampleX, sbin.get(inputSampleX),
124 inputSampleX+1, sbin.get(inputSampleX+1) ) );
125 }
126 this.sbout.set( this.sbout.size()-1, sbin.get(sbin.size()-1) );
127 return this.sbout.getSampleChunk();
128 }
129 }
130 };
131
132 protected SampleBuffer sbout = null;
133
134 /**
135 * Process a sample chunk and output a sample chunk in the given
136 * output format.
137 *
138 * @param s The input sample chunk
139 * @param output The output format
140 * @return A resampled sample chunk.
141 */
142 public abstract SampleChunk process( SampleChunk s, AudioFormat output );
143 }
144
145 /**
146 * Sample rate conversion defaults to
147 * {@link SampleRateConversionAlgorithm#LINEAR_INTERPOLATION}
148 */
149 private SampleRateConversionAlgorithm sampleConverter =
150 SampleRateConversionAlgorithm.LINEAR_INTERPOLATION;
151
152 /** The output format to which sample chunks will be converted */
153 private AudioFormat outputFormat = null;
154
155 /**
156 * Default constructor that takes the input conversion
157 * @param converter The converter to use
158 * @param outputFormat The output format to convert to
159 */
160 public SampleRateConverter( final SampleRateConversionAlgorithm converter,
161 final AudioFormat outputFormat )
162 {
163 this.sampleConverter = converter;
164 this.outputFormat = outputFormat;
165 this.setFormat( outputFormat );
166 }
167
168 /**
169 * Chainable constructor.
170 *
171 * @param as The audio stream to process
172 * @param converter The converter to use
173 * @param outputFormat The output format to convert to
174 */
175 public SampleRateConverter( final AudioStream as, final SampleRateConversionAlgorithm converter,
176 final AudioFormat outputFormat )
177 {
178 super( as );
179 this.sampleConverter = converter;
180 this.outputFormat = outputFormat;
181 this.setFormat( outputFormat );
182 }
183
184 /**
185 * {@inheritDoc}
186 * @see org.openimaj.audio.processor.AudioProcessor#process(org.openimaj.audio.SampleChunk)
187 */
188 @Override
189 public SampleChunk process( final SampleChunk sample ) throws Exception
190 {
191 if( sample.getFormat().getNBits() != this.outputFormat.getNBits() )
192 throw new IllegalArgumentException( "The number of bits in the " +
193 "output format is not the same as the sample chunk. Use a " +
194 "resampling conversion first before using the sample-rate " +
195 "converter." );
196
197 if( sample.getFormat().getNumChannels() != this.outputFormat.getNumChannels() )
198 throw new IllegalArgumentException( "The number of channels in the " +
199 "output format is not the same as the sample chunk. Use a " +
200 "channel converter first before using the sample-rate " +
201 "converter." );
202
203 if( sample.getFormat().getSampleRateKHz() == this.outputFormat.getSampleRateKHz() )
204 return sample;
205
206 final SampleChunk sc = this.sampleConverter.process( sample, this.outputFormat );
207 sc.setStartTimecode( sample.getStartTimecode() );
208 return sc;
209 }
210 }