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 }