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.audio.generation;
34  
35  import java.util.ArrayList;
36  import java.util.List;
37  
38  import org.openimaj.audio.AudioFormat;
39  import org.openimaj.audio.AudioStream;
40  import org.openimaj.audio.Instrument;
41  import org.openimaj.audio.SampleChunk;
42  import org.openimaj.audio.generation.Oscillator.SineOscillator;
43  import org.openimaj.audio.samples.SampleBuffer;
44  import org.openimaj.audio.util.WesternScaleNote;
45  
46  /**
47   * 	Really really basic synthesizer. Useful for doing tests by running the
48   * 	synth as an audio source through filters or whatever.
49   *
50   *  @author David Dupplaw (dpd@ecs.soton.ac.uk)
51   *
52   *	@created 2 May 2012
53   */
54  public class Synthesizer extends AudioStream implements Instrument
55  {
56  	/**
57  	 * 	Interface for options.
58  	 *
59  	 *	@author David Dupplaw (dpd@ecs.soton.ac.uk)
60  	 *  @created 19 Feb 2013
61  	 *	@version $Author$, $Revision$, $Date$
62  	 */
63  	public static interface OscillatorOptions
64  	{
65  	}
66  
67  	/**
68  	 * 	Options class for FM synthesis. Note that the carrier options are set by the
69  	 * 	FM synth and will be overridden.  The modulator options can be controlled
70  	 * 	by the user.
71  	 *
72  	 *	@author David Dupplaw (dpd@ecs.soton.ac.uk)
73  	 *  @created 19 Feb 2013
74  	 *	@version $Author$, $Revision$, $Date$
75  	 */
76  	public static class FMOptions implements OscillatorOptions
77  	{
78  		/** The carrier signal */
79  		public Synthesizer carrier;
80  
81  		/** The modulator signal */
82  		public Synthesizer modulator;
83  
84  		/** The modulator amplitude */
85  		public double modulatorAmplitude = 1000d / Integer.MAX_VALUE;
86  	}
87  
88  	/** The current time position of the synth */
89  	private double currentTime = 0;
90  
91  	/** This is a cached version of the current time * 1000 as it's used lots of places */
92  	private double currentTimeMS = 0;
93  
94  	/** The oscillator used to generate the wave */
95  	private Oscillator oscillator = new SineOscillator();
96  
97  	/** Default sample chunk length is 1024 bytes */
98  	private final int sampleChunkLength = 1024;
99  
100 	/** Default frequency is the standard A4 (440Hz) tuning pitch */
101 	private double frequency = 440;
102 
103 	/** The gain of the synth */
104 	private int gain = Integer.MAX_VALUE;
105 
106 	/** The time the last note on was detected */
107 	private double noteOnTime = 0;
108 
109 	/** Whether to generate a note or not */
110 	private boolean noteOn = true;
111 
112 	/** Determines whether a note off has been detected. */
113 	private boolean noteOff = false;
114 
115 	/** The time the note off was detected */
116 	private double noteOffTime = 0;
117 
118 	/** Envelope parameter - attack */
119 	private long attack = 0;
120 
121 	/** Envelope parameter - decay */
122 	private long decay = 0;
123 
124 	/** Envelope parameter - sustain */
125 	private float sustain = 1f;
126 
127 	/** Envelope parameter - release */
128 	private long release = 0;
129 
130 	/** The current value on the envelope */
131 	private float currentADSRValue = 0;
132 
133 	/** The phase the envelope is in */
134 	private char envelopePhase = 'N';
135 
136 	/** Listeners for synthesizer events */
137 	private final List<SynthesizerListener> listeners =
138 			new ArrayList<SynthesizerListener>();
139 
140 	/**
141 	 *
142 	 */
143 	public Synthesizer()
144     {
145 		this.setFormat( new AudioFormat( 16, 44.1, 1 ) );
146     }
147 
148 	/**
149 	 *	{@inheritDoc}
150 	 * 	@see org.openimaj.audio.AudioStream#nextSampleChunk()
151 	 */
152 	@Override
153     public SampleChunk nextSampleChunk()
154     {
155 		final Oscillator o = this.oscillator;
156 		if( !this.noteOn )
157 			return null;
158 //			o = Oscillator.NONE;
159 
160 	    final SampleChunk x = o.getSampleChunk( this.sampleChunkLength,
161 	    		this.currentTime, this.frequency, this.gain, this.format );
162 
163 	    this.applyADSREnvelope( x.getSampleBuffer() );
164 
165 	    this.currentTime += x.getSampleBuffer().size() /
166 	    	(this.format.getSampleRateKHz()*1000d);
167 	    this.currentTimeMS = this.currentTime * 1000d;
168 
169 	    return x;
170     }
171 
172 	/**
173 	 * 	Set the frequency at which the synth will generate tones.
174 	 *  @param f The frequency
175 	 */
176 	public void setFrequency( final double f )
177 	{
178 		this.frequency = f;
179 	}
180 
181 	/**
182 	 * 	Set the gain at which the synth will generate tones
183 	 *	@param gain The gain
184 	 */
185 	public void setGain( final int gain )
186 	{
187 		this.gain = gain;
188 	}
189 
190 	/**
191 	 * 	Set the type of oscillator used to generate tones.
192 	 *  @param t The type of oscillator.
193 	 */
194 	public void setOscillatorType( final Oscillator t )
195 	{
196 		this.oscillator = t;
197 	}
198 
199 	/**
200 	 * 	Returns the oscillator in use.
201 	 * 	@return the oscillator instance.
202 	 */
203 	public Oscillator getOscillator()
204 	{
205 		return this.oscillator;
206 	}
207 
208 	/**
209 	 *	{@inheritDoc}
210 	 * 	@see org.openimaj.audio.AudioStream#reset()
211 	 */
212 	@Override
213     public void reset()
214     {
215 		this.currentTime = 0;
216     }
217 
218 	/**
219 	 *	{@inheritDoc}
220 	 * 	@see org.openimaj.audio.AudioStream#getLength()
221 	 */
222 	@Override
223 	public long getLength()
224 	{
225 		return -1;
226 	}
227 
228 	/**
229 	 * 	Note on.
230 	 */
231 	public void noteOn()
232 	{
233 		this.noteOn = true;
234 		this.noteOff = false;
235 		this.noteOnTime = this.currentTimeMS;
236 	}
237 
238 	/**
239 	 *	Note off
240 	 */
241 	public void noteOff()
242 	{
243 		this.noteOff  = true;
244 		this.noteOffTime = this.currentTimeMS;
245 	}
246 
247 	/**
248 	 * 	Returns whether the synth is playing a note or not.
249 	 *	@return TRUE if the synth is currently playing a note.
250 	 */
251 	public boolean isNoteOn()
252 	{
253 		return this.noteOn;
254 	}
255 
256 	/**
257 	 * 	Applies the ADSR gain envelope to the samples generated by the
258 	 * 	generator.
259 	 * 	@param sb The sample buffer to affect
260 	 */
261 	public void applyADSREnvelope( final SampleBuffer sb )
262 	{
263 		// The current time in the envelope at the start of processing this buffer
264 		double currentms = this.currentTimeMS - this.noteOnTime;
265 
266 		// The time of each sample in ms
267 		final double sampleTime = 1d / sb.getFormat().getSampleRateKHz();
268 
269 		// Loop through the sample buffer
270 		for( int i = 0; i < sb.size(); i++ )
271 		{
272 			// If the note is set to off and the ADSR value dips to zero,
273 			// then we can say the note is now definitively off.
274 			if( this.noteOff && this.currentADSRValue == 0 && this.envelopePhase == 'R' )
275 			{
276 				this.noteOn = false;
277 				this.fireSynthQuiet();
278 				this.noteOff = false;
279 			}
280 
281 			// Outside the attack phase?
282 			if( currentms > this.attack )
283 			{
284 				// Outside the decay phase?
285 				if( currentms > this.decay+this.attack )
286 				{
287 					if( !this.noteOff )
288 					{
289 						// In the sustain phase.
290 						this.currentADSRValue = this.sustain;
291 						this.envelopePhase = 'S';
292 					}
293 					else
294 					{
295 						// in the release phase.
296 						this.currentADSRValue = this.sustain * Math.max( 0f,
297 							(float)(1d-((currentms - this.noteOffTime) /
298 									this.release)) );
299 						this.envelopePhase = 'R';
300 					}
301 				}
302 				else
303 				{
304 					// In the decay phase
305 					this.currentADSRValue = Math.max( this.sustain, 1f - (float)
306 						((1f - this.sustain) *
307 								(currentms-this.attack) / this.decay ) );
308 					this.envelopePhase = 'D';
309 				}
310 			}
311 			else
312 			{
313 				// In the attack phase
314 				this.currentADSRValue = Math.min( 1f,
315 						(float)(currentms/this.attack) );
316 				this.envelopePhase = 'A';
317 			}
318 
319 			sb.set( i, sb.get(i) * this.currentADSRValue );
320 
321 			currentms += sampleTime;
322 
323 //			System.out.println( currentms +" : "+this.currentADSRValue+" ("+this.envelopePhase+")" );
324 		}
325 
326 //		System.out.println( this.envelopePhase +" : "+this.currentADSRValue );
327 	}
328 
329 	/**
330 	 * 	Returns the phase of the ADSR envelope
331 	 *	@return the envelope phase.
332 	 */
333 	public char getEnvelopePhase()
334 	{
335 		return this.envelopePhase;
336 	}
337 
338 	/**
339 	 * 	Get the ADSR attack time in milliseconds
340 	 *	@return the attack time in milliseconds
341 	 */
342 	public long getAttack()
343 	{
344 		return this.attack;
345 	}
346 
347 	/**
348 	 * 	Set the ADSR attack time in milliseconds
349 	 *	@param attack the attack time in milliseconds
350 	 */
351 	public void setAttack( final long attack )
352 	{
353 		this.attack = attack;
354 	}
355 
356 	/**
357 	 * 	Get the ADSR decay time in milliseconds
358 	 *	@return the decay time in milliseconds
359 	 */
360 	public long getDecay()
361 	{
362 		return this.decay;
363 	}
364 
365 	/**
366 	 * 	Set the ADSR decay time in milliseconds
367 	 *	@param decay the decay time in milliseconds
368 	 */
369 	public void setDecay( final long decay )
370 	{
371 		this.decay = decay;
372 	}
373 
374 	/**
375 	 * 	Get the ADSR sustain gain
376 	 *	@return the sustain gain
377 	 */
378 	public float getSustain()
379 	{
380 		return this.sustain;
381 	}
382 
383 	/**
384 	 * 	Set the ADSR sustain gain
385 	 *	@param sustain the sustain gain
386 	 */
387 	public void setSustain( final float sustain )
388 	{
389 		this.sustain = sustain;
390 	}
391 
392 	/**
393 	 * 	Get the release time in milliseconds
394 	 *	@return the release time in milliseconds
395 	 */
396 	public long getRelease()
397 	{
398 		return this.release;
399 	}
400 
401 	/**
402 	 * 	Set the ADSR release time in milliseconds
403 	 *	@param release the release time in milliseconds
404 	 */
405 	public void setRelease( final long release )
406 	{
407 		this.release = release;
408 	}
409 
410 	/**
411 	 * 	Add a synth listener to this synth.
412 	 *	@param sl The synth listener
413 	 */
414 	public void addSynthesizerListener( final SynthesizerListener sl )
415 	{
416 		this.listeners.add( sl );
417 	}
418 
419 	/**
420 	 * 	remove the synth listener from this synth.
421 	 *	@param sl The synth listener to remove
422 	 */
423 	public void removeSynthesizerListener( final SynthesizerListener sl )
424 	{
425 		this.listeners.remove( sl );
426 	}
427 
428 	/**
429 	 * 	Fired when the synth finished the release phase.
430 	 */
431 	protected void fireSynthQuiet()
432 	{
433 		final ArrayList<SynthesizerListener> l = new ArrayList<SynthesizerListener>( this.listeners );
434 		for( final SynthesizerListener sl : l )
435 			sl.synthQuiet();
436 	}
437 
438 	/**
439 	 *	{@inheritDoc}
440 	 * 	@see org.openimaj.audio.Instrument#noteOn(int, double)
441 	 */
442 	@Override
443 	public void noteOn( final int noteNumber, final double velocity )
444 	{
445 		System.out.println( "Note on "+noteNumber );
446 		this.setGain( (int)(velocity * Integer.MAX_VALUE) );
447 		this.setFrequency( WesternScaleNote.noteToFrequency(noteNumber) );
448 		this.noteOn();
449 	}
450 
451 	/**
452 	 *	{@inheritDoc}
453 	 * 	@see org.openimaj.audio.Instrument#noteOff(int)
454 	 */
455 	@Override
456 	public void noteOff( final int noteNumber )
457 	{
458 		System.out.println( "Note off "+noteNumber );
459 		this.noteOff();
460 	}
461 }