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;
034
035import java.nio.ByteBuffer;
036import java.nio.ByteOrder;
037
038import org.openimaj.audio.samples.SampleBuffer;
039import org.openimaj.audio.samples.SampleBufferFactory;
040import org.openimaj.audio.timecode.AudioTimecode;
041
042/**
043 * Represents a chunk of an audio file and stores the raw audio data. The data
044 * is unnormalised - that is, it is stored in this class in its original format
045 * in the form of a byte array. This is for speed during audio playback.
046 *
047 * If you require normalised data (data as an integer array for example) use the
048 * method {@link #getSamplesAsByteBuffer()} and use the {@link ByteBuffer}'s
049 * methods asXXXBuffer (e.g. ByteBuffer#asShortBuffer) to get the samples in a
050 * normalised form.
051 *
052 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
053 * @created 8 Jun 2011
054 *
055 */
056public class SampleChunk extends Audio
057{
058        /** The samples in the chunk */
059        private byte[] samples = new byte[1];
060
061        /** The timecode of the start of the sample chunk */
062        private AudioTimecode startTimecode = new AudioTimecode(0);
063
064        /**
065         * Create a new SampleChunk buffer with the given audio format, but do not
066         * initialise the samples.
067         *
068         * @param af
069         *            The audio format of the samples
070         */
071        public SampleChunk(final AudioFormat af)
072        {
073                this(new byte[1], af);
074        }
075
076        /**
077         * Create a new sample chunk using the given samples and the given audio
078         * format.
079         *
080         * @param samples
081         *            The samples to initialise with
082         * @param af
083         *            The audio format of the samples
084         */
085        public SampleChunk(final byte[] samples, final AudioFormat af)
086        {
087                this.setSamples(samples);
088                super.format = af;
089        }
090
091        /**
092         * Create a new sample chunk using the given samples and the given audio
093         * format.
094         *
095         * @param samples
096         *            The samples to initialise with
097         * @param af
098         *            The audio format of the samples
099         * @param tc
100         *            The audio timecode of these samples
101         */
102        public SampleChunk(final byte[] samples, final AudioFormat af, final AudioTimecode tc)
103        {
104                this.setSamples(samples);
105                this.startTimecode = tc;
106                super.format = af;
107        }
108
109        /**
110         * Set the samples in this sample chunk.
111         *
112         * @param samples
113         *            the samples in this sample chunk.
114         */
115        public void setSamples(final byte[] samples)
116        {
117                synchronized (this.samples)
118                {
119                        this.samples = samples;
120                }
121        }
122
123        /**
124         * Get the samples in this sample chunk
125         *
126         * @return the samples in this sample chunk
127         */
128        public byte[] getSamples()
129        {
130                return this.samples;
131        }
132
133        /**
134         * Returns the number of samples in this sample chunk. If there are 128
135         * stereo samples, this method will return 256. That is, it does not
136         * normalise for the number of channels. However, it does normalise for the
137         * size of each sample. So if this is a 16-bit buffer of 256 bytes length,
138         * this method will return 128.
139         *
140         * @return the number of samples in this sample chunk.
141         */
142        public int getNumberOfSamples()
143        {
144                return this.samples.length / (this.format.getNBits() / 8);
145        }
146
147        /**
148         * Returns a {@link ByteBuffer} that can be used to create views of the
149         * samples in the object. For example, to get short integers, you can get
150         * {@link #getSamplesAsByteBuffer()}.asShortBuffer()
151         *
152         * @return A {@link ByteBuffer}
153         */
154        public ByteBuffer getSamplesAsByteBuffer()
155        {
156                if (this.samples == null)
157                        return null;
158
159                ByteOrder bo = null;
160
161                if (this.format.isBigEndian())
162                        bo = ByteOrder.BIG_ENDIAN;
163                else
164                        bo = ByteOrder.LITTLE_ENDIAN;
165
166                return ByteBuffer.wrap(this.samples).order(bo);
167        }
168
169        /**
170         * Returns an appropriate sample buffer for this sample chunk. If an
171         * appropriate sample buffer cannot be found, null will be returned. This
172         * will wrap the samples array from the underlying chunk, so you should only
173         * side-affect the samples if you're sure they will not be reused.
174         *
175         * @return An appropriate {@link SampleBuffer}
176         */
177        public SampleBuffer getSampleBuffer()
178        {
179                return SampleBufferFactory.createSampleBuffer(this, this.format);
180        }
181
182        /**
183         * Set the timecode at the start of this audio chunk.
184         *
185         * @param startTimecode
186         *            the timecode at the start of the chunk.
187         */
188        public void setStartTimecode(final AudioTimecode startTimecode)
189        {
190                this.startTimecode = startTimecode;
191        }
192
193        /**
194         * Get the timecode at the start of this audio chunk.
195         *
196         * @return the timecode at the start of the chunk.
197         */
198        public AudioTimecode getStartTimecode()
199        {
200                return this.startTimecode;
201        }
202
203        /**
204         * Return a slice of data from the sample array. The indices are based on
205         * the samples in the byte array, not the bytes themselves.
206         * <p>
207         * The assumption is that samples are whole numbers of bytes. So, if the
208         * sample size was 16-bits, then passing in 2 for the start index would
209         * actually index the byte at index 4 in the underlying sample array. The
210         * order of the bytes is unchanged.
211         *
212         * @param start
213         *            The start index of the sample.
214         * @param length
215         *            The length of the samples get.
216         * @return The sample slice as a new {@link SampleChunk}
217         */
218        public SampleChunk getSampleSlice(final int start, final int length)
219        {
220                final int nBytesPerSample = this.format.getNBits() / 8;
221                final int startSampleByteIndex = start * nBytesPerSample;
222                final byte[] newSamples = new byte[length * nBytesPerSample];
223
224                synchronized (this.samples)
225                {
226                        System.arraycopy(this.samples, startSampleByteIndex,
227                                        newSamples, 0, length * nBytesPerSample);
228                }
229
230                final SampleChunk s = new SampleChunk(this.format);
231                s.setSamples(newSamples);
232
233                // Set the timestamp to the start of this new slice
234                final double samplesPerChannelPerMillisec = this.format.getSampleRateKHz();
235                s.setStartTimecode(new AudioTimecode(
236                                this.getStartTimecode().getTimecodeInMilliseconds() +
237                                (long) (start / samplesPerChannelPerMillisec)));
238
239                return s;
240        }
241
242        /**
243         * Prepends the given samples to the front of this sample chunk. It is
244         * expected that the given samples are in the same format as this sample
245         * chunk; if they are not an exception is thrown. Side-affects this sample
246         * chunk and will return a reference to this sample chunk.
247         *
248         * @param sample
249         *            the samples to add
250         * @return This sample chunk with the bytes prepended
251         */
252        public SampleChunk prepend(final SampleChunk sample)
253        {
254                // Check the sample formats are the same
255                if (!sample.getFormat().equals(this.format))
256                        throw new IllegalArgumentException("Sample types are not equivalent");
257
258                // Get the samples from the given chunk
259                final byte[] x1 = sample.getSamplesAsByteBuffer().array();
260
261                // Create an array for the concatenated pair
262                final byte[] newSamples = new byte[this.samples.length + x1.length];
263
264                // Loop through adding the new samples
265                System.arraycopy(x1, 0, newSamples, 0, x1.length);
266
267                synchronized (this.samples)
268                {
269                        System.arraycopy(this.samples, 0, newSamples, x1.length, this.samples.length);
270                }
271
272                // Update this object
273                this.samples = newSamples;
274                this.setStartTimecode(sample.getStartTimecode().clone());
275                return this;
276        }
277
278        /**
279         * Appends the given samples to the end of this sample chunk. It is expected
280         * that the given samples are in the same format as this sample chunk; if
281         * they are not an exception is thrown. Side-affects this sample chunk and
282         * will return a reference to this sample chunk.
283         *
284         * @param sample
285         *            the samples to add
286         * @return This sample chunk with the bytes appended
287         */
288        public SampleChunk append(final SampleChunk sample)
289        {
290                // Check the sample formats are the same
291                if (!sample.getFormat().equals(this.format))
292                        throw new IllegalArgumentException("Sample types are not equivalent");
293
294                // Get the samples from the given chunk
295                final byte[] x1 = sample.getSamplesAsByteBuffer().array();
296
297                // Create an array for the concatenated pair
298                final byte[] newSamples = new byte[this.samples.length + x1.length];
299
300                synchronized (this.samples)
301                {
302                        System.arraycopy(this.samples, 0, newSamples, 0, this.samples.length);
303                }
304
305                System.arraycopy(x1, 0, newSamples, this.samples.length, x1.length);
306
307                // Update this object
308                this.samples = newSamples;
309                return this;
310        }
311
312        /**
313         * {@inheritDoc}
314         */
315        @Override
316        public SampleChunk clone()
317        {
318                return new SampleChunk(this.samples.clone(), this.format.clone(), this.startTimecode == null ? new AudioTimecode(
319                                0)
320                : this.startTimecode.clone());
321        }
322
323        /**
324         * Pads the sample chunk to the given size with zeros.
325         *
326         * @param requiredSampleSetSize
327         *            The required sample size
328         */
329        public void pad(final int requiredSampleSetSize)
330        {
331                final byte[] samples = new byte[requiredSampleSetSize * (this.format.getNBits() / 8)];
332                System.arraycopy(this.samples, 0, samples, 0, this.samples.length);
333                this.samples = samples;
334        }
335}