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