/*
* 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 java.util.ArrayList;
import ddf.minim.javax.sound.sampled.AudioFileFormat;
import ddf.minim.javax.sound.sampled.AudioFormat;
import ddf.minim.spi.AudioOut;
import ddf.minim.spi.AudioRecording;
import ddf.minim.spi.AudioRecordingStream;
import ddf.minim.spi.AudioStream;
import ddf.minim.spi.MinimServiceProvider;
import ddf.minim.spi.SampleRecorder;
/**
* The <code>Minim</code> class is the starting point for most everything you
* will do with this library. There are methods for obtaining objects for
* playing audio files: AudioSample and AudioPlayer. There are methods for
* obtaining an AudioRecorder, which is how you record audio to disk. There are
* methods for obtaining an AudioInput, which is how you can monitor the
* computer's line-in or microphone, depending on what the user has set as the
* record source. Finally there are methods for obtaining an AudioOutput, which
* is how you can play audio generated by your program, typically by connecting
* classes found in the ugens package.
* <p>
* Minim keeps references to all of the resources that are returned from these
* various methods so that you don't have to worry about closing them. Instead,
* when your application ends you can simply call the stop method of your Minim
* instance. Processing users <em>do not</em> need to do this because Minim
* detects when a PApplet is passed to the contructor and registers for a
* notification of application shutdown.
* <p>
* Minim requires an Object that can handle two important file system operations
* so that it doesn't have to worry about details of the current environment.
* These two methods are:
* <p>
* <code>
* String sketchPath( String fileName )<br/>
* InputStream createInput( String fileName )<br/>
* </code>
* </p>
* These are methods that are defined in Processing, which Minim was originally
* designed to cleanly interface with. The <code>sketchPath</code> method is
* expected to transform a filename into an absolute path and is used when
* attempting to create an AudioRecorder. The <code>createInput</code> method is
* used when loading files and is expected to take a filename, which is not
* necessarily an absolute path, and return an <code>InputStream</code> that can
* be used to read the file. For example, in Processing, the
* <code>createInput</code> method will search in the data folder, the sketch
* folder, handle URLs, and absolute paths. If you are using Minim outside of
* Processing, you can handle whatever cases are appropriate for your project.
*
* @example Basics/PlayAFile
*
* @author Damien Di Fede
*/
public class Minim {
/** Specifies that you want a MONO AudioInput or AudioOutput */
public static final int MONO = 1;
/** Specifies that you want a STEREO AudioInput or AudioOutput */
public static final int STEREO = 2;
public static final int LOOP_CONTINUOUSLY = -1;
/** The .wav file format. */
public static AudioFileFormat.Type WAV = AudioFileFormat.Type.WAVE;
/** The .aiff file format. */
public static AudioFileFormat.Type AIFF = AudioFileFormat.Type.AIFF;
/** The .aifc file format. */
public static AudioFileFormat.Type AIFC = AudioFileFormat.Type.AIFC;
/** The .au file format. */
public static AudioFileFormat.Type AU = AudioFileFormat.Type.AU;
/** The .snd file format. */
public static AudioFileFormat.Type SND = AudioFileFormat.Type.SND;
private static boolean DEBUG = false;
private MinimServiceProvider mimp = null;
// we keep track of all the resources we are asked to create
// so that when shutting down the library, users can simply call stop(),
// and don't have to call close() on all of the things they've created.
// in the event that they *do* call close() on resource we've created,
// it will be removed from this list.
private ArrayList<AudioSource> sources = new ArrayList<AudioSource>();
// and unfortunately we have to track stream separately
private ArrayList<AudioStream> streams = new ArrayList<AudioStream>();
/**
* @invisible
*
* Creates an instance of Minim that will use the provided
* implementation for audio.
*
* @param implementation
* the MinimServiceProvider that will be used for returning audio
* resources
*/
public Minim(MinimServiceProvider implementation) {
mimp = implementation;
mimp.start();
}
/**
* @invisible
*
* Used internally to report error messages. These error messages
* will appear in the console area of the PDE if you are running
* a sketch from the PDE, otherwise they will appear in the Java
* Console.
*
* @param message
* the error message to report
*/
public static void error(String message) {
System.out.println("=== Minim Error ===");
System.out.println("=== " + message);
System.out.println();
}
/**
* @invisible
*
* Displays a debug message, but only if {@link #debugOn()} has
* been called. The message will be displayed in the console area
* of the PDE, if you are running your sketch from the PDE.
* Otherwise, it will be displayed in the Java Console.
*
* @param message
* the message to display
* @see #debugOn()
*/
public static void debug(String message) {
if (DEBUG) {
String[] lines = message.split("\n");
System.out.println("=== Minim Debug ===");
for (int i = 0; i < lines.length; i++) {
System.out.println("=== " + lines[i]);
}
System.out.println();
}
}
/**
* Turns on debug messages.
*/
public void debugOn() {
DEBUG = true;
if (mimp != null) {
mimp.debugOn();
}
}
/**
* Turns off debug messages.
*
*/
public void debugOff() {
DEBUG = false;
if (mimp != null) {
mimp.debugOff();
}
}
/**
* @invisible
*
* Library callback used by Processing when a sketch is being
* shutdown. It is not necessary to call this directly. It simply
* calls stop().
*
*
*/
public void dispose() {
stop();
}
/**
*
* Stops Minim and releases all audio resources.
* <p>
* If using Minim outside of Processing, you must call this to release all
* of the audio resources that Minim has generated. It will call close() on
* all of them for you.
*
*/
public void stop() {
debug("Stopping Minim...");
// close all sources and release them
for (AudioSource s : sources) {
// null the parent so the AudioSource doesn't try to call
// removeSource
s.parent = null;
s.close();
}
sources.clear();
for (AudioStream s : streams) {
s.close();
}
// stop the implementation
mimp.stop();
}
void addSource(AudioSource s) {
sources.add(s);
s.parent = this;
}
void removeSource(AudioSource s) {
sources.remove(s);
}
/**
* Creates an AudioSample using the provided sample data and AudioFormat.
* When a buffer size is not provided, it defaults to 1024. The buffer size
* of a sample controls the size of the left, right, and mix AudioBuffer
* fields of the returned AudioSample.
*
* @shortdesc Creates an AudioSample using the provided sample data and
* AudioFormat.
*
* @param sampleData
* float[]: the single channel of sample data
* @param format
* the AudioFormat describing the sample data
*
* @return an AudioSample that can be triggered to make sound
*
* @example Advanced/CreateAudioSample
*
* @related AudioSample
*/
public AudioSample createSample(float[] sampleData, AudioFormat format) {
return createSample(sampleData, format, 1024);
}
/**
* Creates an AudioSample using the provided sample data and AudioFormat,
* with the desired output buffer size.
*
* @param sampleData
* float[]: the single channel of sample data
* @param format
* the AudioFormat describing the sample data
* @param bufferSize
* int: the output buffer size to use, which controls the size of
* the left, right, and mix AudioBuffer fields of the returned
* AudioSample.
*
* @return an AudioSample that can be triggered to make sound
*/
public AudioSample createSample(float[] sampleData, AudioFormat format,
int bufferSize) {
AudioSample sample = mimp
.getAudioSample(sampleData, format, bufferSize);
addSource(sample);
return sample;
}
/**
* Creates an AudioSample using the provided left and right channel sample
* data with an output buffer size of 1024.
*
* @param leftSampleData
* float[]: the left channel of the sample data
* @param rightSampleData
* float[]: the right channel of the sample data
* @param format
* the AudioFormat describing the sample data
*
* @return an AudioSample that can be triggered to make sound
*/
public AudioSample createSample(float[] leftSampleData,
float[] rightSampleData, AudioFormat format) {
return createSample(leftSampleData, rightSampleData, format, 1024);
}
/**
* Creates an AudioSample using the provided left and right channel sample
* data.
*
* @param leftSampleData
* float[]: the left channel of the sample data
* @param rightSampleData
* float[]: the right channel of the sample data
* @param format
* the AudioFormat describing the sample data
* @param bufferSize
* int: the output buffer size to use, which controls the size of
* the left, right, and mix AudioBuffer fields of the returned
* AudioSample.
*
* @return an AudioSample that can be triggered to make sound
*/
public AudioSample createSample(float[] leftSampleData,
float[] rightSampleData, AudioFormat format, int bufferSize) {
AudioSample sample = mimp.getAudioSample(leftSampleData,
rightSampleData, format, bufferSize);
addSource(sample);
return sample;
}
/**
* Loads the requested file into an AudioSample. By default, the buffer size
* used is 1024.
*
* @shortdesc Loads the requested file into an AudioSample.
*
* @param filename
* the file or URL that you want to load
*
* @return an AudioSample that can be triggered to make sound
*
* @example Basics/TriggerASample
*
* @see #loadSample(String, int)
* @see AudioSample
* @related AudioSample
*/
public AudioSample loadSample(String filename) {
return loadSample(filename, 1024);
}
/**
* Loads the requested file into an AudioSample.
*
* @param filename
* the file or URL that you want to load
* @param bufferSize
* int: The sample buffer size you want. This controls the size
* of the left, right, and mix AudioBuffer fields of the returned
* AudioSample.
*
* @return an AudioSample that can be triggered to make sound
*/
public AudioSample loadSample(String filename, int bufferSize) {
AudioSample sample = mimp.getAudioSample(filename, bufferSize);
addSource(sample);
return sample;
}
/**
* @invisible Loads the requested file into an {@link AudioSnippet}
*
* @param filename
* the file or URL you want to load
* @return an <code>AudioSnippet</code> of the requested file or URL
*/
@Deprecated
public AudioSnippet loadSnippet(String filename) {
AudioRecording c = mimp.getAudioRecording(filename);
if (c != null) {
return new AudioSnippet(c);
} else {
Minim.error("Couldn't load the file " + filename);
}
return null;
}
/**
* Loads the requested file into an AudioPlayer. The default buffer size is
* 1024 samples and the buffer size determines the size of the left, right,
* and mix AudioBuffer fields on the returned AudioPlayer.
*
* @shortdesc Loads the requested file into an AudioPlayer.
*
* @example Basics/PlayAFile
*
* @param filename
* the file or URL you want to load
* @return an <code>AudioPlayer</code> that plays the file
*
* @related AudioPlayer
*
* @see #loadFile(String, int)
*/
public AudioPlayer loadFile(String filename) {
return loadFile(filename, 1024);
}
/**
* Loads the requested file into an {@link AudioPlayer} with the request
* buffer size.
*
* @param filename
* the file or URL you want to load
* @param bufferSize
* int: the sample buffer size you want, which determines the
* size of the left, right, and mix AudioBuffer fields of the
* returned AudioPlayer.
*
* @return an <code>AudioPlayer</code> with a sample buffer of the requested
* size
*/
public AudioPlayer loadFile(String filename, int bufferSize) {
AudioPlayer player = null;
AudioRecordingStream rec = mimp.getAudioRecordingStream(filename,
bufferSize, false);
if (rec != null) {
AudioFormat format = rec.getFormat();
AudioOut out = mimp.getAudioOutput(format.getChannels(),
bufferSize, format.getSampleRate(),
format.getSampleSizeInBits());
if (out != null) {
player = new AudioPlayer(rec, out);
} else {
rec.close();
}
}
if (player != null) {
addSource(player);
} else {
error("Couldn't load the file " + filename);
}
return player;
}
/**
* Loads the file into an AudioRecordingStream, which allows you to stream
* audio data from the file yourself. Note that doing this will not result
* in any sound coming out of your speakers, unless of course you send it
* there. You would primarily use this to perform offline-analysis of a file
* or for very custom sound streaming schemes.
*
* @shortdesc Loads the file into an AudioRecordingStream.
*
* @example Analysis/offlineAnalysis
*
* @param filename
* the file to load
* @param bufferSize
* int: the bufferSize to use, which controls how much of the
* streamed file is stored in memory at a time.
* @param inMemory
* boolean: whether or not the file should be cached in memory as
* it is read
*
* @return an AudioRecordingStream that you can use to read from the file.
*
*
*/
public AudioRecordingStream loadFileStream(String filename, int bufferSize,
boolean inMemory) {
AudioRecordingStream stream = mimp.getAudioRecordingStream(filename,
bufferSize, inMemory);
streams.add(stream);
return stream;
}
/**
* Loads the requested file into a MultiChannelBuffer. The buffer's channel
* count and buffer size will be adjusted to match the file.
*
* @shortdesc Loads the requested file into a MultiChannelBuffer.
*
* @example Advanced/loadFileIntoBuffer
*
* @param filename
* the file to load
* @param outBuffer
* the MultiChannelBuffer to fill with the file's audio samples
*
* @return the sample rate of audio samples in outBuffer, or 0 if the load
* failed.
*
* @related MultiChannelBuffer
*/
public float loadFileIntoBuffer(String filename,
MultiChannelBuffer outBuffer) {
final int readBufferSize = 4096;
float sampleRate = 0;
AudioRecordingStream stream = mimp.getAudioRecordingStream(filename,
readBufferSize, false);
if (stream != null) {
// stream.open();
stream.play();
sampleRate = stream.getFormat().getSampleRate();
final int channelCount = stream.getFormat().getChannels();
// for reading the file in, in chunks.
MultiChannelBuffer readBuffer = new MultiChannelBuffer(
channelCount, readBufferSize);
// make sure the out buffer is the correct size and type.
outBuffer.setChannelCount(channelCount);
// how many samples to read total
final long totalSampleCount = stream.getSampleFrameLength();
outBuffer.setBufferSize((int) totalSampleCount);
// now read in chunks.
long totalSamplesRead = 0;
while (totalSamplesRead < totalSampleCount) {
// is the remainder smaller than our buffer?
if (totalSampleCount - totalSamplesRead < readBufferSize) {
readBuffer
.setBufferSize((int) (totalSampleCount - totalSamplesRead));
}
stream.read(readBuffer);
// copy data from one buffer to the other.
for (int i = 0; i < channelCount; ++i) {
// a faster way to do this would be nice.
for (int s = 0; s < readBuffer.getBufferSize(); ++s) {
outBuffer.setSample(i, (int) totalSamplesRead + s,
readBuffer.getSample(i, s));
}
}
totalSamplesRead += readBuffer.getBufferSize();
}
stream.close();
} else {
debug("Unable to load an AudioRecordingStream for " + filename);
}
return sampleRate;
}
/**
* Creates an AudioRecorder that will use the provided Recordable object as
* its record source and that will save to the file name specified.
* Recordable classes in Minim include AudioOutput, AudioInput, AudioPlayer,
* and AudioSample. The format of the file will be inferred from the
* extension in the file name. If the extension is not a recognized file
* type, this will return null.
*
* @shortdesc Creates an AudioRecorder.
*
* @example Basics/RecordAudioOutput
*
* @param source
* the <code>Recordable</code> object you want to use as a record
* source
* @param fileName
* the name of the file to record to
*
* @return an <code>AudioRecorder</code> for the record source
*
* @related AudioRecorder
*/
public AudioRecorder createRecorder(Recordable source, String fileName) {
return createRecorder(source, fileName, false);
}
/**
* Creates an AudioRecorder that will use the provided Recordable object as
* its record source and that will save to the file name specified.
* Recordable classes in Minim include AudioOutput, AudioInput, AudioPlayer,
* and AudioSample. The format of the file will be inferred from the
* extension in the file name. If the extension is not a recognized file
* type, this will return null. Be aware that if you choose buffered
* recording the call to AudioRecorder's save method will block until the
* entire buffer is written to disk. In the event that the buffer is very
* large, your app will noticeably hang.
*
* @shortdesc Creates an AudioRecorder.
*
* @example Basics/RecordAudioOutput
*
* @param source
* the <code>Recordable</code> object you want to use as a record
* source
* @param fileName
* the name of the file to record to
* @param buffered
* boolean: whether or not to use buffered recording
*
* @return an <code>AudioRecorder</code> for the record source
*
* @related AudioRecorder
* @invisible
*/
public AudioRecorder createRecorder(Recordable source, String fileName,
boolean buffered) {
SampleRecorder rec = mimp.getSampleRecorder(source, fileName, buffered);
if (rec != null) {
return new AudioRecorder(source, rec);
} else {
error("Couldn't create an AudioRecorder for " + fileName + ".");
}
return null;
}
/**
* An AudioInput is used when you want to monitor the active audio input of
* the computer. On a laptop, for instance, this will typically be the
* built-in microphone. On a desktop it might be the line-in port on the
* soundcard. The default values are for a stereo input with a 1024 sample
* buffer (ie the size of left, right, and mix buffers), sample rate of
* 44100 and bit depth of 16. Generally speaking, you will not want to
* specify these things, but it's there if you need it.
*
* @shortdesc get an AudioInput that reads from the active audio input of
* the soundcard
*
* @return an AudioInput that reads from the active audio input of the
* soundcard
*
* @see #getLineIn(int, int, float, int)
* @related AudioInput
* @example Basics/MonitorInput
*/
public AudioInput getLineIn() {
return getLineIn(STEREO);
}
/**
* Gets either a MONO or STEREO {@link AudioInput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @return an <code>AudioInput</code> with the requested type, a 1024 sample
* buffer, a sample rate of 44100 and a bit depth of 16
* @see #getLineIn(int, int, float, int)
*/
public AudioInput getLineIn(int type) {
return getLineIn(type, 1024, 44100, 16);
}
/**
* Gets an {@link AudioInput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @param bufferSize
* int: how long you want the <code>AudioInput</code>'s sample
* buffer to be (ie the size of left, right, and mix buffers)
* @return an <code>AudioInput</code> with the requested attributes, a
* sample rate of 44100 and a bit depth of 16
* @see #getLineIn(int, int, float, int)
*/
public AudioInput getLineIn(int type, int bufferSize) {
return getLineIn(type, bufferSize, 44100, 16);
}
/**
* Gets an {@link AudioInput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @param bufferSize
* int: how long you want the <code>AudioInput</code>'s sample
* buffer to be (ie the size of left, right, and mix buffers)
* @param sampleRate
* float: the desired sample rate in Hertz (typically 44100)
* @return an <code>AudioInput</code> with the requested attributes and a
* bit depth of 16
* @see #getLineIn(int, int, float, int)
*/
public AudioInput getLineIn(int type, int bufferSize, float sampleRate) {
return getLineIn(type, bufferSize, sampleRate, 16);
}
/**
* Gets an {@link AudioInput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @param bufferSize
* int: how long you want the <code>AudioInput</code>'s sample
* buffer to be (ie the size of left, right, and mix buffers)
* @param sampleRate
* float: the desired sample rate in Hertz (typically 44100)
* @param bitDepth
* int: the desired bit depth (typically 16)
* @return an <code>AudioInput</code> with the requested attributes
*/
public AudioInput getLineIn(int type, int bufferSize, float sampleRate,
int bitDepth) {
AudioInput input = null;
AudioStream stream = mimp.getAudioInput(type, bufferSize, sampleRate,
bitDepth);
if (stream != null) {
AudioOut out = mimp.getAudioOutput(type, bufferSize, sampleRate,
bitDepth);
if (out != null) {
input = new AudioInput(stream, out);
} else {
stream.close();
}
}
if (input != null) {
addSource(input);
} else {
error("Minim.getLineIn: attempt failed, could not secure an AudioInput.");
}
return input;
}
/**
* Get the input as an AudioStream that you can read from yourself, rather
* than wrapped in an AudioInput that does that work for you.
*
* @param type
* Minim.MONO or Minim.STEREO
* @param bufferSize
* int: how long you want the AudioStream's interal buffer to be.
* @param sampleRate
* float: the desired sample rate in Hertz (typically 44100)
* @param bitDepth
* int: the desired bit depth (typically 16)
* @return an AudioStream that reads from the input source of the soundcard.
*/
public AudioStream getInputStream(int type, int bufferSize,
float sampleRate, int bitDepth) {
AudioStream stream = mimp.getAudioInput(type, bufferSize, sampleRate,
bitDepth);
streams.add(stream);
return stream;
}
/**
* An AudioOutput is used to generate sound in real-time and output it to
* the soundcard. Usually, the sound generated by an AudioOutput will be
* heard through the speakers or headphones attached to a computer. The
* default parameters for an AudioOutput are STEREO sound, a 1024 sample
* buffer (ie the size of the left, right, and mix buffers), a sample rate
* of 44100, and a bit depth of 16. To actually generate sound with an
* AudioOutput you need to patch at least one sound generating UGen to it,
* such as an Oscil.
* <p>
* Using setOutputMixer you can also create AudioOutputs that send sound to
* specific output channels of a soundcard.
*
* @example Basics/SynthesizeSound
*
* @shortdesc get an AudioOutput that can be used to generate audio
*
* @return an AudioOutput that can be used to generate audio
* @see #getLineOut(int, int, float, int)
* @related AudioOutput
* @related UGen
*/
public AudioOutput getLineOut() {
return getLineOut(STEREO);
}
/**
* Gets an {@link AudioOutput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @return an <code>AudioOutput</code> with the requested type, a 1024
* sample buffer, a sample rate of 44100 and a bit depth of 16
* @see #getLineOut(int, int, float, int)
*/
public AudioOutput getLineOut(int type) {
return getLineOut(type, 1024, 44100, 16);
}
/**
* Gets an {@link AudioOutput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @param bufferSize
* int: how long you want the AudioOutput's sample buffer to be
* (ie the size of the left, right, and mix buffers)
* @return an <code>AudioOutput</code> with the requested attributes, a
* sample rate of 44100 and a bit depth of 16
* @see #getLineOut(int, int, float, int)
*/
public AudioOutput getLineOut(int type, int bufferSize) {
return getLineOut(type, bufferSize, 44100, 16);
}
/**
* Gets an {@link AudioOutput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @param bufferSize
* int: how long you want the AudioOutput's sample buffer to be
* (ie the size of the left, right, and mix buffers)
* @param sampleRate
* float: the desired sample rate in Hertz (typically 44100)
* @return an <code>AudioOutput</code> with the requested attributes and a
* bit depth of 16
* @see #getLineOut(int, int, float, int)
*/
public AudioOutput getLineOut(int type, int bufferSize, float sampleRate) {
return getLineOut(type, bufferSize, sampleRate, 16);
}
/**
* Gets an {@link AudioOutput}.
*
* @param type
* Minim.MONO or Minim.STEREO
* @param bufferSize
* int: how long you want the AudioOutput's sample buffer to be
* (ie the size of the left, right, and mix buffers)
* @param sampleRate
* float: the desired sample rate in Hertz (typically 44100)
* @param bitDepth
* int: the desired bit depth (typically 16)
* @return an <code>AudioOutput</code> with the requested attributes
*/
public AudioOutput getLineOut(int type, int bufferSize, float sampleRate,
int bitDepth) {
AudioOut out = mimp.getAudioOutput(type, bufferSize, sampleRate,
bitDepth);
if (out != null) {
AudioOutput output = new AudioOutput(out);
addSource(output);
return output;
}
error("Minim.getLineOut: attempt failed, could not secure a LineOut.");
return null;
}
}