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.util.Iterator;
036
037import org.apache.commons.lang.NotImplementedException;
038import org.openimaj.audio.AudioFormat;
039import org.openimaj.audio.SampleChunk;
040import org.openimaj.audio.timecode.AudioTimecode;
041
042/**
043 *      A {@link SampleBuffer} for 8 bit sample chunks.
044 *
045 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
046 *      @created 23rd November 2011
047 */
048public class SampleBuffer8Bit implements SampleBuffer, Iterator<Float>
049{
050        /** Scalar to convert integer to byte */
051        private final static int SAMPLE_SCALAR = Integer.MAX_VALUE / Byte.MAX_VALUE;
052
053        /** The byte buffer */
054        private byte[] byteBuffer = null;
055
056        /** The audio format of the samples */
057        private AudioFormat format = null;
058
059        /** The iterator counter for iterating over the samples */
060        private int iteratorCount;
061
062        /** The timecode of this sample buffer */
063        private AudioTimecode timecode;
064
065        /**
066         *      Create a new 8-bit sample buffer using the given
067         *      samples and the given audio format.
068         *
069         *      @param samples The samples to buffer.
070         *      @param af The audio format.
071         */
072        public SampleBuffer8Bit( final SampleChunk samples, final AudioFormat af )
073        {
074                this.format = af;
075                if( this.format == null || this.format.getNBits() != 8 )
076                        throw new IllegalArgumentException( "Number of bits " +
077                                        "must be 8 if you're instantiating an 8 bit " +
078                                        "sample buffer. However "+
079                                        (this.format==null?"format object was null.":
080                                        "number of bits in format was "+this.format.getNBits()));
081
082                this.byteBuffer = samples.getSamples();
083                this.timecode = samples.getStartTimecode();
084        }
085
086        /**
087         *      Create a new sample buffer with the given format and
088         *      the given number of samples. It does not scale for
089         *      the number of channels in the audio format, so you must pre-multiply
090         *      the number of samples by the number of channels if you are only
091         *      counting samples per channel.
092         *
093         *      @param af The {@link AudioFormat} of the samples
094         *      @param nSamples The number of samples
095         */
096        public SampleBuffer8Bit( final AudioFormat af, final int nSamples )
097        {
098                this.format = af.clone();
099                if( this.format == null || this.format.getNBits() != 8 )
100                        throw new IllegalArgumentException( "Number of bits " +
101                                        "must be 8 if you're instantiating an 8 bit " +
102                                        "sample buffer. However "+
103                                        (this.format==null?"format object was null.":
104                                        "number of bits in format was "+this.format.getNBits()));
105
106                this.byteBuffer = new byte[ nSamples ];
107        }
108
109        /**
110         *      {@inheritDoc}
111         *      @see org.openimaj.audio.samples.SampleBuffer#getSampleChunk()
112         */
113        @Override
114        public SampleChunk getSampleChunk()
115        {
116                final SampleChunk sc = new SampleChunk( this.byteBuffer, this.format );
117                sc.setStartTimecode( this.timecode );
118                return sc;
119        }
120
121        /**
122         *      {@inheritDoc}
123         *
124         *      Note that because we cannot use native methods for copying parts of
125         *      an array, we must use Java methods so this will be considerably
126         *      slower than {@link #getSampleChunk()}.
127         *
128         *      @see org.openimaj.audio.samples.SampleBuffer#getSampleChunk(int)
129         */
130        @Override
131        public SampleChunk getSampleChunk( final int channel )
132        {
133                if( channel > this.format.getNumChannels() )
134                        throw new IllegalArgumentException( "Cannot generate sample chunk " +
135                                        "for channel "+channel+" as sample only has " +
136                                        this.format.getNumChannels() + " channels." );
137
138                if( channel == 0 && this.format.getNumChannels() == 1 )
139                        return this.getSampleChunk();
140
141                final byte[] newSamples = new byte[this.size()];
142                for( int i = 0; i < this.size(); i++ )
143                        newSamples[i] = this.byteBuffer[i*this.format.getNumChannels() + channel];
144
145                final AudioFormat af = this.format.clone();
146                af.setNumChannels( 1 );
147                return new SampleChunk( newSamples, af );
148        }
149
150        /**
151         *      {@inheritDoc}
152         *      @see org.openimaj.audio.samples.SampleBuffer#get(int)
153         */
154        @Override
155        public float get( final int index )
156        {
157                // Convert the byte to an integer
158                return this.byteBuffer[index] * SampleBuffer8Bit.SAMPLE_SCALAR;
159        }
160
161        /**
162         *      {@inheritDoc}
163         *      @see org.openimaj.audio.samples.SampleBuffer#getUnscaled(int)
164         */
165        @Override
166        public float getUnscaled( final int index )
167        {
168                return this.byteBuffer[index];
169        }
170
171        /**
172         *      {@inheritDoc}
173         *      @see org.openimaj.audio.samples.SampleBuffer#set(int, float)
174         */
175        @Override
176        public void set( final int index, float sample )
177        {
178                if( sample > Byte.MAX_VALUE )
179                        sample = Byte.MAX_VALUE;
180                if( sample < Byte.MIN_VALUE )
181                        sample = Byte.MIN_VALUE;
182
183                this.byteBuffer[index] = (byte)(sample / SampleBuffer8Bit.SAMPLE_SCALAR);
184        }
185
186        /**
187         *      {@inheritDoc}
188         *      @see org.openimaj.audio.samples.SampleBuffer#size()
189         */
190        @Override
191        public int size()
192        {
193                return this.byteBuffer.length;
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);
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 );
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}