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        /** The underlying byte array we're wrapping */
053        private byte[] samples = null;
054
055        /** The short buffer that we're wrapping */
056        private ShortBuffer shortBuffer = null;
057
058        /** The audio format of the samples */
059        private AudioFormat format;
060
061        /** A counter used for iterating over the samples */
062        private int iteratorCount;
063
064        /** The timecode of this buffer */
065        private AudioTimecode timecode = null;
066
067        /**
068         * Create a new 16-bit sample buffer using the given samples and the given
069         * audio format.
070         *
071         * @param samples
072         *            The samples to buffer.
073         * @param af
074         *            The audio format.
075         */
076        public SampleBuffer16Bit(final SampleChunk samples, final AudioFormat af) {
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 sample format at the
085         * given size. It does not scale for the number of channels in the audio
086         * format, so you must pre-multiply the number of samples by the number of
087         * channels if you are only counting samples per channel.
088         *
089         * @param af
090         *            The audio format of the samples
091         * @param nSamples
092         *            The number of samples
093         */
094        public SampleBuffer16Bit(final AudioFormat af, final int nSamples) {
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         * 
104         * @see org.openimaj.audio.samples.SampleBuffer#getSampleChunk()
105         */
106        @Override
107        public SampleChunk getSampleChunk() {
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 an
117         * array, we must use Java methods so this will be considerably slower than
118         * {@link #getSampleChunk()}.
119         *
120         * @see org.openimaj.audio.samples.SampleBuffer#getSampleChunk(int)
121         */
122        @Override
123        public SampleChunk getSampleChunk(final int channel) {
124                if (channel > this.format.getNumChannels())
125                        throw new IllegalArgumentException("Cannot generate sample chunk " +
126                                        "for channel " + channel + " as sample only has " +
127                                        this.format.getNumChannels() + " channels.");
128
129                if (channel == 0 && this.format.getNumChannels() == 1)
130                        return this.getSampleChunk();
131
132                final byte[] newSamples = new byte[this.size() * 2];
133                final ShortBuffer sb = ByteBuffer.wrap(newSamples).order(
134                                this.format.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN).asShortBuffer();
135                for (int i = 0; i < this.size() / this.format.getNumChannels(); i++)
136                        sb.put(i, this.shortBuffer.get(i * this.format.getNumChannels() + channel));
137
138                final AudioFormat af = this.format.clone();
139                af.setNumChannels(1);
140                return new SampleChunk(newSamples, af);
141        }
142
143        /**
144         * {@inheritDoc}
145         * 
146         * @see org.openimaj.audio.samples.SampleBuffer#get(int)
147         */
148        @Override
149        public float get(final int index) {
150                if (index >= this.shortBuffer.limit())
151                        return 0;
152
153                // Convert the short to an integer
154                return (float) this.shortBuffer.get(index) * Integer.MAX_VALUE / Short.MAX_VALUE;
155        }
156
157        /**
158         * {@inheritDoc}
159         * 
160         * @see org.openimaj.audio.samples.SampleBuffer#getUnscaled(int)
161         */
162        @Override
163        public float getUnscaled(final int index) {
164                return this.shortBuffer.get(index);
165        }
166
167        /**
168         * {@inheritDoc}
169         * 
170         * @see org.openimaj.audio.samples.SampleBuffer#set(int, float)
171         */
172        @Override
173        public void set(final int index, final float sample) {
174                // Clipping
175                float s = sample;
176                if (s > Integer.MAX_VALUE)
177                        s = Integer.MAX_VALUE;
178                if (s < Integer.MIN_VALUE)
179                        s = Integer.MIN_VALUE;
180
181                this.shortBuffer.put(index, (short) (sample * Short.MAX_VALUE / Integer.MAX_VALUE));
182        }
183
184        /**
185         * {@inheritDoc}
186         * 
187         * @see org.openimaj.audio.samples.SampleBuffer#size()
188         */
189        @Override
190        public int size() {
191                return this.shortBuffer.limit();
192        }
193
194        /**
195         * {@inheritDoc}
196         * 
197         * @see org.openimaj.audio.samples.SampleBuffer#getFormat()
198         */
199        @Override
200        public AudioFormat getFormat() {
201                return this.format;
202        }
203
204        /**
205         * {@inheritDoc}
206         * 
207         * @see org.openimaj.audio.samples.SampleBuffer#setFormat(org.openimaj.audio.AudioFormat)
208         */
209        @Override
210        public void setFormat(final AudioFormat af) {
211                this.format = af;
212        }
213
214        /**
215         * {@inheritDoc}
216         * 
217         * @see org.openimaj.audio.samples.SampleBuffer#asDoubleArray()
218         */
219        @Override
220        public double[] asDoubleArray() {
221                final double[] d = new double[this.size()];
222                for (int i = 0; i < this.size(); i++)
223                        d[i] = this.get(i) / Integer.MAX_VALUE;
224                return d;
225        }
226
227        /**
228         * {@inheritDoc}
229         * 
230         * @see org.openimaj.audio.samples.SampleBuffer#asDoubleChannelArray()
231         */
232        @Override
233        public double[][] asDoubleChannelArray() {
234                final int nc = this.format.getNumChannels();
235                final double[][] s = new double[nc][this.size() / nc];
236                for (int c = 0; c < nc; c++)
237                        for (int sa = 0; sa < this.size() / nc; sa++)
238                                s[c][sa] = this.get(sa * nc + c) / Integer.MAX_VALUE;
239                return s;
240        }
241
242        /**
243         * {@inheritDoc}
244         * 
245         * @see org.openimaj.audio.samples.SampleBuffer#asDoubleArray()
246         */
247        @Override
248        public float[] asFloatArray() {
249                final float[] d = new float[this.size()];
250                for (int i = 0; i < this.size(); i++)
251                        d[i] = this.get(i) / Integer.MAX_VALUE;
252                return d;
253        }
254
255        /**
256         * {@inheritDoc}
257         * 
258         * @see org.openimaj.audio.samples.SampleBuffer#asDoubleChannelArray()
259         */
260        @Override
261        public float[][] asFloatChannelArray() {
262                final int nc = this.format.getNumChannels();
263                final float[][] s = new float[nc][this.size() / nc];
264                for (int c = 0; c < nc; c++)
265                        for (int sa = 0; sa < this.size() / nc; sa++)
266                                s[c][sa] = this.get(sa * nc + c) / Integer.MAX_VALUE;
267                return s;
268        }
269
270        /**
271         * {@inheritDoc}
272         * 
273         * @see java.lang.Iterable#iterator()
274         */
275        @Override
276        public Iterator<Float> iterator() {
277                this.iteratorCount = 0;
278                return this;
279        }
280
281        /**
282         * {@inheritDoc}
283         * 
284         * @see java.util.Iterator#hasNext()
285         */
286        @Override
287        public boolean hasNext() {
288                return this.iteratorCount < this.size();
289        }
290
291        /**
292         * {@inheritDoc}
293         * 
294         * @see java.util.Iterator#next()
295         */
296        @Override
297        public Float next() {
298                final float f = this.get(this.iteratorCount);
299                this.iteratorCount++;
300                return f;
301        }
302
303        /**
304         * {@inheritDoc}
305         * 
306         * @see java.util.Iterator#remove()
307         */
308        @Override
309        public void remove() {
310                throw new NotImplementedException("Cannot remove from 16bit sample buffer");
311        }
312
313        @Override
314        public AudioTimecode getStartTimecode() {
315                return this.timecode;
316        }
317
318        /**
319         * Set the timecode for this buffer.
320         * 
321         * @param timecode
322         *            The timecode
323         */
324        public void setStartTimecode(final AudioTimecode timecode) {
325                this.timecode = timecode;
326        }
327}