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.samples;
034
035import java.nio.ByteBuffer;
036import java.nio.ByteOrder;
037import java.nio.ShortBuffer;
038import java.util.Iterator;
039
040import org.apache.commons.lang.NotImplementedException;
041import org.openimaj.audio.AudioFormat;
042import org.openimaj.audio.SampleChunk;
043import org.openimaj.audio.timecode.AudioTimecode;
044
045/**
046 *      A {@link SampleBuffer} for 16-bit sample chunks.
047 *
048 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
049 *      @created 23rd November 2011
050 */
051public class SampleBuffer16Bit implements SampleBuffer, Iterator<Float>
052{
053        /** The underlying byte array we're wrapping */
054        private byte[] samples = null;
055
056        /** The short buffer that we're wrapping */
057        private ShortBuffer shortBuffer = null;
058
059        /** The audio format of the samples */
060        private AudioFormat format;
061
062        /** A counter used for iterating over the samples */
063        private int iteratorCount;
064
065        /** The timecode of this buffer */
066        private AudioTimecode timecode = null;
067
068        /**
069         *      Create a new 16-bit sample buffer using the given
070         *      samples and the given audio format.
071         *
072         *      @param samples The samples to buffer.
073         *      @param af The audio format.
074         */
075        public SampleBuffer16Bit( final SampleChunk samples, final AudioFormat af )
076        {
077                this.format = af;
078                this.shortBuffer = samples.getSamplesAsByteBuffer().asShortBuffer();
079                this.samples = samples.getSamples();
080                this.setStartTimecode( samples.getStartTimecode() );
081        }
082
083        /**
084         *      Create a new 16-bit sample buffer using the given
085         *      sample format at the given size. It does not scale for
086         *      the number of channels in the audio format, so you must pre-multiply
087         *      the number of samples by the number of channels if you are only
088         *      counting samples per channel.
089         *
090         *      @param af The audio format of the samples
091         *      @param nSamples The number of samples
092         */
093        public SampleBuffer16Bit( final AudioFormat af, final int nSamples )
094        {
095                this.format = af.clone();
096                this.samples = new byte[ nSamples * 2 ];
097                this.shortBuffer = new SampleChunk(this.samples,this.format)
098                        .getSamplesAsByteBuffer().asShortBuffer();
099        }
100
101        /**
102         *      {@inheritDoc}
103         *      @see org.openimaj.audio.samples.SampleBuffer#getSampleChunk()
104         */
105        @Override
106        public SampleChunk getSampleChunk()
107        {
108                final SampleChunk sc = new SampleChunk( this.samples, this.format );
109                sc.setStartTimecode( this.timecode );
110                return sc;
111        }
112
113        /**
114         *      {@inheritDoc}
115         *
116         *      Note that because we cannot use native methods for copying parts of
117         *      an array, we must use Java methods so this will be considerably
118         *      slower than {@link #getSampleChunk()}.
119         *
120         *      @see org.openimaj.audio.samples.SampleBuffer#getSampleChunk(int)
121         */
122        @Override
123        public SampleChunk getSampleChunk( final int channel )
124        {
125                if( channel > this.format.getNumChannels() )
126                        throw new IllegalArgumentException( "Cannot generate sample chunk " +
127                                        "for channel "+channel+" as sample only has " +
128                                        this.format.getNumChannels() + " channels." );
129
130                if( channel == 0 && this.format.getNumChannels() == 1 )
131                        return this.getSampleChunk();
132
133                final byte[] newSamples = new byte[this.size()*2];
134                final ShortBuffer sb = ByteBuffer.wrap( newSamples ).order(
135                        this.format.isBigEndian()?ByteOrder.BIG_ENDIAN:ByteOrder.LITTLE_ENDIAN ).
136                        asShortBuffer();
137                for( int i = 0; i < this.size()/this.format.getNumChannels(); i++ )
138                        sb.put( i, this.shortBuffer.get( i*this.format.getNumChannels() + channel ) );
139
140                final AudioFormat af = this.format.clone();
141                af.setNumChannels( 1 );
142                return new SampleChunk( newSamples, af );
143        }
144
145        /**
146         *      {@inheritDoc}
147         *      @see org.openimaj.audio.samples.SampleBuffer#get(int)
148         */
149        @Override
150        public float get( final int index )
151        {
152                if( index >= this.shortBuffer.limit() )
153                        return 0;
154
155                // Convert the short to an integer
156                return (float)this.shortBuffer.get(index) * Integer.MAX_VALUE / Short.MAX_VALUE;
157        }
158
159        /**
160         *      {@inheritDoc}
161         *      @see org.openimaj.audio.samples.SampleBuffer#getUnscaled(int)
162         */
163        @Override
164        public float getUnscaled( final int index )
165        {
166                return this.shortBuffer.get(index);
167        }
168
169        /**
170         *      {@inheritDoc}
171         *      @see org.openimaj.audio.samples.SampleBuffer#set(int, float)
172         */
173        @Override
174        public void set( final int index, final float sample )
175        {
176                // Clipping
177                float s = sample;
178                if( s > Integer.MAX_VALUE )
179                        s = Integer.MAX_VALUE;
180                if( s < Integer.MIN_VALUE )
181                        s = Integer.MIN_VALUE;
182
183                this.shortBuffer.put( index, (short)(sample  * Short.MAX_VALUE / Integer.MAX_VALUE) );
184        }
185
186        /**
187         *      {@inheritDoc}
188         *      @see org.openimaj.audio.samples.SampleBuffer#size()
189         */
190        @Override
191        public int size()
192        {
193                return this.shortBuffer.limit();
194        }
195
196        /**
197         *      {@inheritDoc}
198         *      @see org.openimaj.audio.samples.SampleBuffer#getFormat()
199         */
200        @Override
201        public AudioFormat getFormat()
202        {
203                return this.format;
204        }
205
206        /**
207         *      {@inheritDoc}
208         *      @see org.openimaj.audio.samples.SampleBuffer#setFormat(org.openimaj.audio.AudioFormat)
209         */
210        @Override
211        public void setFormat( final AudioFormat af )
212        {
213                this.format = af;
214        }
215
216        /**
217         *      {@inheritDoc}
218         *      @see org.openimaj.audio.samples.SampleBuffer#asDoubleArray()
219         */
220        @Override
221        public double[] asDoubleArray()
222        {
223                final double[] d = new double[this.size()];
224                for( int i = 0; i < this.size(); i++ )
225                        d[i] = this.get(i) / Integer.MAX_VALUE;
226                return d;
227        }
228
229        /**
230         *      {@inheritDoc}
231         *      @see org.openimaj.audio.samples.SampleBuffer#asDoubleChannelArray()
232         */
233        @Override
234        public double[][] asDoubleChannelArray()
235        {
236                final int nc = this.format.getNumChannels();
237                final double[][] s = new double[nc][this.size()/nc];
238                for( int c = 0; c < nc; c++ )
239                        for( int sa = 0; sa < this.size()/nc; sa++ )
240                                s[c][sa] = this.get( sa*nc + c )  / Integer.MAX_VALUE;
241                return s;
242        }
243
244        /**
245         *      {@inheritDoc}
246         *      @see java.lang.Iterable#iterator()
247         */
248        @Override
249        public Iterator<Float> iterator()
250        {
251                this.iteratorCount = 0;
252                return this;
253        }
254
255        /**
256         *      {@inheritDoc}
257         *      @see java.util.Iterator#hasNext()
258         */
259        @Override
260        public boolean hasNext()
261        {
262                return this.iteratorCount < this.size();
263        }
264
265        /**
266         *      {@inheritDoc}
267         *      @see java.util.Iterator#next()
268         */
269        @Override
270        public Float next()
271        {
272                final float f = this.get(this.iteratorCount);
273                this.iteratorCount++;
274                return f;
275        }
276
277        /**
278         *      {@inheritDoc}
279         *      @see java.util.Iterator#remove()
280         */
281        @Override
282        public void remove()
283        {
284                throw new NotImplementedException( "Cannot remove from 16bit sample buffer" );
285        }
286
287        @Override
288        public AudioTimecode getStartTimecode()
289        {
290                return this.timecode;
291        }
292
293        /**
294         *      Set the timecode for this buffer.
295         *      @param timecode The timecode
296         */
297        public void setStartTimecode( final AudioTimecode timecode )
298        {
299                this.timecode = timecode;
300        }
301}