/* * Copyright (c) 2007 - 2008 by Damien Di Fede <ddf@compartmental.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package ddf.minim; import ddf.minim.spi.AudioOut; import ddf.minim.ugens.DefaultInstrument; import ddf.minim.ugens.Frequency; import ddf.minim.ugens.Instrument; import ddf.minim.ugens.Summer; /** * <p> * An AudioOutput is a connection to the output of a computer's sound card. * Typically the computer speakers are connected to this. * You can use an AudioOutput to do real-time sound synthesis by patching * UGens to an output object. You can get an AudioOutput object from Minim * using one of five methods: * </p> * <pre> * AudioOutput getLineOut() * * // specifiy either Minim.MONO or Minim.STEREO for type * AudioOutput getLineOut(int type) * * // bufferSize is the size of the left, right, * // and mix buffers of the output you get back * AudioOutput getLineOut(int type, int bufferSize) * * // sampleRate is a request for an output of a certain sample rate * AudioOutput getLineOut(int type, int bufferSize, float sampleRate) * * // bitDepth is a request for an output with a certain bit depth * AudioInput getLineOut(int type, int bufferSize, float sampleRate, int bitDepth) * </pre> * <p> * In the event that an output doesn't exist with the requested parameters, * Minim will spit out an error and return null. * In general, you will want to use one of the first two methods listed above. * </p> * <p> * In addition to directly patching UGens to the output, you can also schedule * "notes" to be played by the output at some time in the future. This can * be very powerful when writing algorithmic music and sound. See the playNote * method for more information. * </p> * * @author Damien Di Fede * @related Minim * @related UGen * @related playNote ( ) * * @example Basics/SynthesizeSound * @example Basics/SequenceSound */ public class AudioOutput extends AudioSource implements Polyphonic { // the synth attach our signals to private AudioOut synth; // the signals added by the user private SignalChain signals; // the note manager for this output private NoteManager noteManager; // the Bus for UGens used by this output Summer bus; private class SampleGenerator implements AudioSignal { public void generate(float[] signal) { if ( signals.size() > 0 ) { signals.generate( signal ); } float[] tick = new float[1]; for ( int i = 0; i < signal.length; ++i ) { noteManager.tick(); bus.tick( tick ); signal[i] += tick[0]; } } public void generate(float[] left, float[] right) { if ( signals.size() > 0 ) { signals.generate( left, right ); } float[] tick = new float[2]; for ( int i = 0; i < left.length; ++i ) { noteManager.tick(); bus.tick( tick ); left[i] += tick[0]; right[i] += tick[1]; } } } /** * Constructs an <code>AudioOutput</code> that will use <code>out</code> * to generate sound. * * @param out * the <code>AudioOut</code> that does most of our work * * @invisible */ public AudioOutput(AudioOut out) { super( out ); synth = out; signals = new SignalChain(); noteManager = new NoteManager( getFormat().getSampleRate() ); bus = new Summer(); // configure it bus.setSampleRate( getFormat().getSampleRate() ); bus.setChannelCount( getFormat().getChannels() ); synth.setAudioSignal( new SampleGenerator() ); } /** @deprecated */ public void addSignal(AudioSignal signal) { signals.add( signal ); } /** @deprecated */ public AudioSignal getSignal(int i) { // get i+1 because the bus is signal 0. return signals.get( i ); } /** @deprecated */ public void removeSignal(AudioSignal signal) { signals.remove( signal ); } /** @deprecated */ public AudioSignal removeSignal(int i) { // remove i+1 because the bus is 1 return signals.remove( i ); } /** @deprecated */ public void clearSignals() { signals.clear(); } /** @deprecated */ public void disableSignal(int i) { // disable i+1 because the bus is 0 signals.disable( i ); } /** @deprecated */ public void disableSignal(AudioSignal signal) { signals.disable( signal ); } /** @deprecated */ public void enableSignal(int i) { signals.enable( i ); } /** @deprecated */ public void enableSignal(AudioSignal signal) { signals.enable( signal ); } /** @deprecated */ public boolean isEnabled(AudioSignal signal) { return signals.isEnabled( signal ); } /** @deprecated */ public boolean isSounding() { for ( int i = 1; i < signals.size(); i++ ) { if ( signals.isEnabled( signals.get( i ) ) ) { return true; } } return false; } /** @deprecated */ public void noSound() { for ( int i = 1; i < signals.size(); i++ ) { signals.disable( i ); } } /** @deprecated */ public int signalCount() { return signals.size(); } /** @deprecated */ public void sound() { for ( int i = 1; i < signals.size(); i++ ) { signals.enable( i ); } } /** @deprecated */ public boolean hasSignal(AudioSignal signal) { return signals.contains( signal ); } /** * playNote is a method of scheduling a "note" to be played at * some time in the future (or immediately), where a "note" is * an instance of a class that implements the Instrument interface. * The Instrument interface requires you to implement a noteOn method * that accepts a float duration value and is called when that * Instrument should begin making sound, and a noteOff method * that is called when that Instrument should stop making sound. * <p> * Versions of playNote that do not have an Instrument argument * will create an instance of a default Instrument that plays a * sine tone based on the parameters passed in. * <p> * To facilitate writing algorithmic music, the start time and * duration of a note is expressed in <em>beats</em> and not in seconds. * By default, the tempo of an AudioOutput will be 60 BPM (beats per minute), * which means that beats are equivalent to seconds. If you want to think * in seconds when writing your note playing code, then simply don't change * the tempo of the output. * <p> * Another thing to keep in mind is that the AudioOutput processes its * note queue in its own Thread, so if you are going to queue up a lot of * notes at once you will want to use the pauseNotes method before queuing * them. If you don't, the timing will be slightly off because the "now" that * the start time of each note is an offset from will change from note to note. * Once all of your notes have been added, you call resumeNotes to allow * the AudioOutput to process notes again. * * @related Instrument * @related setTempo ( ) * @related setNoteOffset ( ) * @related setDurationFactor ( ) * @related pauseNotes ( ) * @related resumeNotes ( ) * * @example Basics/SequenceSound * * @shortdesc Schedule a "note" to played by the output. * * @param startTime * float: when the note should begin playing, in beats * @param duration * float: how long the note should be, in beats * @param instrument * the Instrument that will play the note */ public void playNote(float startTime, float duration, Instrument instrument) { noteManager.addEvent( startTime, duration, instrument ); } /** * Schedule a "note" to played by the output that uses the default Instrument. * * @see #playNote(float, float, Instrument) * * @param startTime * float: when the note should begin playing, in beats * @param duration * float: how long the note should be, in beats * @param hz * float: the frequency, in Hertz, of the note to be played */ public void playNote(float startTime, float duration, float hz) { noteManager.addEvent( startTime, duration, new DefaultInstrument( hz, this ) ); } /** * Schedule a "note" to played by the output that uses the default Instrument. * * @see #playNote(float, float, Instrument) * * @param startTime * float: when the note should begin playing, in beats * @param duration * float: how long the note should be, in beats * @param pitchName * String: the pitch name of the note to be played (e.g. "A4" or "Bb3") */ public void playNote(float startTime, float duration, String pitchName) { noteManager.addEvent( startTime, duration, new DefaultInstrument( Frequency.ofPitch( pitchName ).asHz(), this ) ); } /** * Schedule a "note" to played by the output that uses the default Instrument and has a duration of 1 beat. * * @see #playNote(float, float, Instrument) * * @param startTime * float: when the note should begin playing, in beats * @param hz * float: the frequency, in Hertz, of the note to be played */ public void playNote(float startTime, float hz) { noteManager.addEvent( startTime, 1.0f, new DefaultInstrument( hz, this ) ); } /** * Schedule a "note" to played by the output that uses the default Instrument and has a duration of 1 beat. * * @see #playNote(float, float, Instrument) * * @param startTime * float: when the note should begin playing, in beats * @param pitchName * String: the pitch name of the note to be played (e.g. "A4" or "Bb3") */ public void playNote(float startTime, String pitchName) { noteManager.addEvent( startTime, 1.0f, new DefaultInstrument( Frequency.ofPitch( pitchName ).asHz(), this ) ); } /** * Schedule a "note" to played by the output that uses the default Instrument, has a duration of 1 beat, * and is played immediately. * * @see #playNote(float, float, Instrument) * * @param hz * float: the frequency, in Hertz, of the note to be played */ public void playNote(float hz) { noteManager.addEvent( 0.0f, 1.0f, new DefaultInstrument( hz, this ) ); } /** * Schedule a "note" to played by the output that uses the default Instrument, * has a duration of 1 beat, and is played immediately. * * @see #playNote(float, float, Instrument) * * @param pitchName * String: the pitch name of the note to be played (e.g. "A4" or "Bb3") */ public void playNote(String pitchName) { noteManager.addEvent( 0.0f, 1.0f, new DefaultInstrument( Frequency.ofPitch( pitchName ).asHz(), this ) ); } /** * Schedule a "note" to played by the output that uses the default Instrument, * has a duration of 1 beat, is played immediately, and has a pitch of "A4". * This is good to use if you just want to generate some test tones. * * @see #playNote(float, float, Instrument) */ public void playNote() { noteManager.addEvent( 0.0f, 1.0f, new DefaultInstrument( Frequency.ofPitch( "" ).asHz(), this ) ); } /** * The tempo of an AudioOutput controls how it will interpret the start time and duration * arguments of playNote methods. By default the tempo of an AudioOutput is 60 BPM (beats per minute), * which means that one beat lasts one second. Setting the tempo to 120 BPM means that one beat lasts * half of a second. When the tempo is changed, it will only effect playNote calls made * <em>after</em> the change. * * @shortdesc Set the tempo of the AudioOutput to change the meaning of start times and durations for notes. * * @example Basics/SequenceSound * * @param tempo * float: the new tempo for the AudioOutput, in BPM (beats per minute) * * @related getTempo ( ) */ public void setTempo(float tempo) { noteManager.setTempo( tempo ); } /** * Return the current tempo of the AudioOuput. * Tempo is expressed in BPM (beats per minute). * * @return float: the current tempo * * @example Basics/SequenceSound * * @related setTempo ( ) */ public float getTempo() { return noteManager.getTempo(); } /** * When writing out musical scores in code, it is often nice to think about * music in sections, where all of the playNote calls have start times relative to * the beginning of the section. The setNoteOffset method facilitates this by * letting you set a time from which all start times passed to playNote calls * will add on to. So, if you set the note offset to 16, that means all playNote * start times will be relative to the 16th beat from "now". * <p> * By default, note offset is 0. * * @shortdesc Sets the amount of time added to all start times passed to playNote calls. * * @param noteOffset * float: the amount of time added to all start times passed to playNote calls. * * @example Basics/SequenceSound * * @related getNoteOffset ( ) */ public void setNoteOffset(float noteOffset) { noteManager.setNoteOffset( noteOffset ); } /** * Return the current value of the note offset for this output. * * @return float: the current note offset * * @example Basics/SequenceSound * * @related setNoteOffset ( ) */ public float getNoteOffset() { return noteManager.getNoteOffset(); } /** * The duration factor of an AudioOutput defines how durations passed to playNote calls * are scaled before being queued. If your duration factor is 0.5 and you queue a note * with a duration of 2, the actual duration will become 1. This might be useful if * you want to queue a string of notes first with long durations and then very short durations. * <p> * By default the duration factor is 1. * * @shortdesc Sets a factor that will scale durations passed to subsequent playNote calls. * * @param durationFactor * float: the duration factor * * @related getDurationFactor ( ) */ public void setDurationFactor(float durationFactor) { noteManager.setDurationFactor( durationFactor ); } /** * Return the current value of the duration factor for this output. * * @return float: the current duration factor * * @related setDurationFactor ( ) */ public float getDurationFactor() { return noteManager.getDurationFactor(); } /** * An AudioOutput processes its note queue in its own Thread, * so if you are going to queue up a lot of notes at once * you will want to use the <code>pauseNotes</code> method before queuing * them. If you don't, the timing will be slightly off because the "now" that * the start time of each note is an offset from will change from note to note. * Once all of your notes have been added, you call <code>resumeNotes</code> to allow * the AudioOutput to process notes again. * * @shortdesc pause note processing * * @example Basics/SequenceSound * * @related resumeNotes ( ) */ public void pauseNotes() { noteManager.pause(); } /** * Resume note processing. * * @example Basics/SequenceSound * * @see #pauseNotes() * @related pauseNotes ( ) */ public void resumeNotes() { noteManager.resume(); } }