View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  /**
31   *
32   */
33  package org.openimaj.vis.audio;
34  
35  import org.openimaj.audio.AudioFormat;
36  import org.openimaj.audio.samples.FloatSampleBuffer;
37  import org.openimaj.audio.samples.SampleBuffer;
38  import org.openimaj.image.MBFImage;
39  import org.openimaj.util.array.ArrayUtils;
40  import org.openimaj.vis.VisualisationImpl;
41  
42  /**
43   *	A visualisation for signals. It utilises the {@link SampleBuffer} class
44   *	to store the samples to be displayed. It will display multi-channel signals
45   *	as given by the audio format of the SampleBuffer.
46   *	<p>
47   *	A method for accepting sample chunks is implemented so that a "live" display
48   *	of audio waveform can be displayed.
49   *	<p>
50   *	If you prefer to display an overview of the complete audio of a resource,
51   *	use the {@link AudioOverviewVisualisation}.
52   *
53   *	@author David Dupplaw (dpd@ecs.soton.ac.uk)
54   *  @created 13 Jul 2012
55   *	@version $Author$, $Revision$, $Date$
56   */
57  public class AudioWaveform extends VisualisationImpl<SampleBuffer>
58  {
59  	/** */
60  	private static final long serialVersionUID = 1L;
61  
62  	/** Whether to have a decay on the waveform */
63  	private boolean decay = false;
64  
65  	/** The decay amount if decay is set to true */
66  	private float decayAmount = 0.3f;
67  
68  	/** The maximum signal value */
69  	private float maxValue = 100f;
70  
71  	/** The scalar in the x direction */
72  	private float xScale = 1f;
73  
74  	/** Whether to automatically determine the x scalar */
75  	private boolean autoFit = true;
76  
77  	/** Whether to automatically determine the y scalar */
78  	private boolean autoScale = true;
79  
80  	/** The colour to draw the waveform */
81  	private Float[] colour = new Float[]{1f,1f,1f,1f};
82  
83  	/**
84  	 *	Create an audio waveform display of the given width and height
85  	 *	@param w The width of the image
86  	 *	@param h The height of the image
87  	 */
88  	public AudioWaveform( final int w, final int h )
89  	{
90  		super( w, h );
91  	}
92  
93  	/**
94  	 * 	Create an audio waveform that overlays the given visualisation.
95  	 * 	@param v The visualisation to overlay
96  	 */
97  	public AudioWaveform( final VisualisationImpl<?> v )
98  	{
99  		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 }