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.vis.audio;
034
035import org.openimaj.audio.AudioFormat;
036import org.openimaj.audio.samples.FloatSampleBuffer;
037import org.openimaj.audio.samples.SampleBuffer;
038import org.openimaj.image.MBFImage;
039import org.openimaj.util.array.ArrayUtils;
040import org.openimaj.vis.VisualisationImpl;
041
042/**
043 *      A visualisation for signals. It utilises the {@link SampleBuffer} class
044 *      to store the samples to be displayed. It will display multi-channel signals
045 *      as given by the audio format of the SampleBuffer.
046 *      <p>
047 *      A method for accepting sample chunks is implemented so that a "live" display
048 *      of audio waveform can be displayed.
049 *      <p>
050 *      If you prefer to display an overview of the complete audio of a resource,
051 *      use the {@link AudioOverviewVisualisation}.
052 *
053 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
054 *  @created 13 Jul 2012
055 *      @version $Author$, $Revision$, $Date$
056 */
057public class AudioWaveform extends VisualisationImpl<SampleBuffer>
058{
059        /** */
060        private static final long serialVersionUID = 1L;
061
062        /** Whether to have a decay on the waveform */
063        private boolean decay = false;
064
065        /** The decay amount if decay is set to true */
066        private float decayAmount = 0.3f;
067
068        /** The maximum signal value */
069        private float maxValue = 100f;
070
071        /** The scalar in the x direction */
072        private float xScale = 1f;
073
074        /** Whether to automatically determine the x scalar */
075        private boolean autoFit = true;
076
077        /** Whether to automatically determine the y scalar */
078        private boolean autoScale = true;
079
080        /** The colour to draw the waveform */
081        private Float[] colour = new Float[]{1f,1f,1f,1f};
082
083        /**
084         *      Create an audio waveform display of the given width and height
085         *      @param w The width of the image
086         *      @param h The height of the image
087         */
088        public AudioWaveform( final int w, final int h )
089        {
090                super( w, h );
091        }
092
093        /**
094         *      Create an audio waveform that overlays the given visualisation.
095         *      @param v The visualisation to overlay
096         */
097        public AudioWaveform( final VisualisationImpl<?> v )
098        {
099                super( v );
100        }
101
102        /**
103         *      Draw the given sample chunk into an image and returns that image.
104         *      The image is reused, so if you want to keep it you must clone
105         *      the image afterwards.
106         *
107         *      @param sb The sample chunk to draw
108         *      @return The image drawn
109         */
110        public MBFImage drawWaveform( final SampleBuffer sb )
111        {
112                synchronized( this.visImage )
113                {
114                        // If decay is set we lower the values of the data already in place
115                        if( !this.clearBeforeDraw && this.decay )
116                                this.visImage.multiplyInplace( this.decayAmount );
117
118                        // Work out the y scalar
119                        float m = this.maxValue;
120                        if( this.autoScale )
121                                m = (float)Math.max(
122                                                Math.abs( ArrayUtils.minValue( sb.asDoubleArray() ) ),
123                                                Math.abs( ArrayUtils.maxValue( sb.asDoubleArray() ) ) );
124
125                        final int nc = sb.getFormat().getNumChannels();
126                        final int channelHeight = this.visImage.getHeight()/nc;
127                        final float scalar = this.visImage.getHeight() / (m*2*nc);
128                        final int h = this.getHeight();
129
130                        // Work out the xscalar
131                        if( this.autoFit )
132                                this.xScale = this.visImage.getWidth() / (sb.size()/(float)nc);
133
134                        // Plot the wave form
135                        for( int c = 0; c < nc; c++ )
136                        {
137                                final int yOffset = channelHeight * c + channelHeight/2;
138                                int lastX = 0;
139                                int lastY = yOffset;
140                                for( int i = 1; i < sb.size()/nc; i += nc )
141                                {
142                                        final int x = (int)(i*this.xScale);
143                                        final int y = (int)(sb.get(i*nc+c)/Integer.MAX_VALUE*scalar+yOffset);
144                                        this.visImage.drawLine( lastX, lastY, x, h-y, this.colour );
145                                        lastX = x;
146                                        lastY = h-y;
147                                }
148                        }
149                }
150
151                return this.visImage;
152        }
153
154        /**
155         *      {@inheritDoc}
156         *      @see org.openimaj.vis.VisualisationImpl#update()
157         */
158        @Override
159        public void update()
160        {
161                if( this.data != null )
162                        synchronized( this.data )
163                        {
164                                this.drawWaveform( this.data );
165                        }
166        }
167
168        /**
169         *      If the samples are represented as a set of doubles, you can set them
170         *      here. The assumed format will be a single channel at 44.1KHz.
171         *      @param samples The sample data.
172         */
173        public void setData( final double[] samples )
174        {
175                final FloatSampleBuffer fsb = new FloatSampleBuffer( samples,
176                                new AudioFormat( -1, 44.1, 1 ) );
177                super.setData( fsb );
178        }
179
180        /**
181         *      Samples represented as a set of doubles.
182         *      @param samples The samples.
183         *      @param format The format of the samples
184         */
185        public void setData( final double[] samples, final AudioFormat format )
186        {
187                final FloatSampleBuffer fsb = new FloatSampleBuffer( samples,
188                                format.clone().setNBits( -1 ) );
189                super.setData( fsb );
190        }
191
192        /**
193         *      Set the maximum value that the signal can achieve. This method also
194         *      disables autoScale.
195         *
196         *      @param f The maximum that a signal can achieve.
197         */
198        public void setMaximum( final float f )
199        {
200                this.maxValue = f;
201                this.autoScale = false;
202        }
203
204        /**
205         *      Get the maximum value in use
206         *      @return The maximum value
207         */
208        public float getMaximum()
209        {
210                return this.maxValue;
211        }
212
213        /**
214         *      Set the x-scale at which to draw the waveform. This method also disables
215         *      auto fit.
216         *      @param f The scale at which to draw the waveform.
217         */
218        public void setXScale( final float f )
219        {
220                this.xScale = f;
221                this.autoFit = false;
222        }
223
224        /**
225         *      Whether to auto fit the x-axis
226         *      @param tf TRUE to auto-fit the x-axis
227         */
228        public void setAutoFit( final boolean tf )
229        {
230                this.autoFit = tf;
231        }
232
233        /**
234         *      Whether to auto fit the y-axis
235         *      @param tf TRUE to auto-fit the y-axis
236         */
237        public void setAutoScale( final boolean tf )
238        {
239                this.autoScale = tf;
240        }
241
242        /**
243         *      Returns whether decay is set
244         *      @return TRUE if decay is set
245         */
246        public boolean isDecay()
247        {
248                return this.decay;
249        }
250
251        /**
252         *      Set whether decay is to be used
253         *      @param decay TRUE for decay, FALSE otherwise
254         */
255        public void setDecay( final boolean decay )
256        {
257                this.decay = decay;
258        }
259
260        /**
261         *      Get the amount of decay in use
262         *      @return The decay amount
263         */
264        public float getDecayAmount()
265        {
266                return this.decayAmount;
267        }
268
269        /**
270         *      Set the amount of decay to use
271         *      @param decayAmount the decay amount
272         */
273        public void setDecayAmount( final float decayAmount )
274        {
275                this.decayAmount = decayAmount;
276        }
277
278        /**
279         *      Set the colour to draw the signal
280         *      @param colour The colour
281         */
282        public void setColour( final Float[] colour )
283        {
284                this.colour = colour;
285        }
286}