jm.audio.RTMixer Maven / Gradle / Ivy
Show all versions of jmusic Show documentation
/*
Copyright (C) 2000 Andrew Sorensen & Andrew Brown
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jm.audio;
import jm.music.rt.RTLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* RTMixer is responsible for convolving the audio signals being pulled from
* n number of RTLines. RTMixer uses a single JMF Java Sound SourceDataLine
* object for writing the newly convolved signal to the audio device.
* Buffers of audio sample data are passed to the SourceDataLine at a rate
* set by the Control Rate value. The contol rate sets the size of the audio
* buffers used by RTMixer, SourceDataLine and Instrument.
* Information about sample rate and channels is retrieved from the first
* audio object of the first instrument of the first RTLine. Therefore,
* there is an assumption that all instruments have the same sample rate and
* number of channels.
*
* @author Andrew Sorensen
* @version 1.0, Sun Feb 25 18:42:43 2001
*/
public class RTMixer implements AudioChainListener {
//------------------------------------------
//Attributes
//------------------------------------------
/**
* A Timer which keeps track of how many samples have been written since
* this object started
*/
public long currentTime = 0;
/**
* The sampleRate to be used when establishing the JMF SourceDataLine
*/
protected int sampleRate;
/**
* The number of channels to be used when setting up SourceDataLine
*/
protected int channels;
/**
* The control rate is used to set how often in instrument returns a full
* buffer of audio samples. This is acheived by changing the size of the
* instrument buffers.
* It is critical to the audio performance and can be tuned to the suit
* the different computers. Smaller values are prefereable because they give
* jMusic audio less latency, but values that are too small will cause
* the audio system to choke and splutter.
*/
protected double controlRate;//0.1 - 0.005
/**
* The number of lines in the RTLine array
*/
private int totLines = 0;
/**
* count shows how many RTLines have passed RTMixer their full buffers
*/
private int count = 0;
/**
* sampleArray contains the convolution of all RTLines buffers
*/
private float[] sampleArray;
/**
* bos is used to convert sampleArray into a byte stream
*/
private ByteArrayOutputStream bos;
/**
* dos is used to help convert sampleArray into bos
*/
private DataOutputStream dos;
/**
* dline is the JFM java sound object which we write sampleArray to
*/
private SourceDataLine dline;
/**
* How far into the score we are in terms of beats
*/
private double scorePosition = 0.0;
/**
* RTLines associated with this RTMixer object
*/
private RTLine[] rtlines;
/**
* buffer size
*/
private int bufferSize;
//-------------------------------------
//Constructors
//-------------------------------------
/**
* The RTMixer constructor sets a number of attributes and opens a JMF java
* sound sourceDataLine.
*
* @param inst the Instruments to be processed by this object.
*/
public RTMixer(RTLine[] rtlines) {
this(rtlines, 0.1); // value is the default control rate
}
/**
* The RTMixer constructor sets a number of attributes and opens a JMF java
* sound sourceDataLine.
*
* @param inst - The Instruments to be processed by this object.
* @param controlRate - Sets the regularity of audio parameter change.
* The control rate is used to set how often in instrument returns a full
* buffer of audio samples. This is acheived by changing the size of the
* instrument buffers.
* Control rate is critical to the audio performance and can be tuned to the suit
* different computers. Smaller values are prefereable because the resulting
* smaller buffer equates to a less latency, but values that are too small will cause
* the audio system to choke and splutter. Values typically fall in the rage 0.01 - 0.005.
*/
public RTMixer(RTLine[] rtlines, double controlRate) {
this.rtlines = rtlines;
this.sampleRate = rtlines[0].getSampleRate();
this.channels = rtlines[0].getChannels();
this.controlRate = controlRate;
this.bufferSize = (int) (this.sampleRate * this.channels * this.controlRate);
while (this.bufferSize % 4 != 0) {
this.controlRate += 0.001;
this.bufferSize = (int) (this.sampleRate * this.channels * this.controlRate);
}
// feed info down to lines and instruments
for (int i = 0; i < rtlines.length; i++) {
this.totLines += this.rtlines[i].getNumLines();
this.rtlines[i].setBufferSize(this.bufferSize);
// check for matching values
if (rtlines[i].getSampleRate() != this.sampleRate) {
System.err.println("jMusic RTMixer error: All instruments must have the same sample rate.");
System.exit(0);
}
if (rtlines[i].getChannels() != this.channels) {
System.err.println("jMusic RTMixer error: All instruments must have the same number of channels.");
System.exit(0);
}
}
initJMFSound(bufferSize);
bos = new ByteArrayOutputStream();
dos = new DataOutputStream(bos);
}
//-------------------------------------
//Abstract Methods
//-------------------------------------
//--------------------------------------
//Public Methods
//---------------------------------------
public void addLines(RTLine[] lines) {
RTLine[] temp = new RTLine[this.totLines + lines.length];
for (int i = 0; i < this.rtlines.length; i++) {
temp[i] = this.rtlines[i];
}
for (int i = this.rtlines.length; i < temp.length; i++) {
// check for matching values
if (rtlines[i].getSampleRate() != this.sampleRate) {
System.err.println("jMusic RTMixer error: All instruments must have the same sample rate.");
System.exit(0);
}
if (rtlines[i].getChannels() != this.channels) {
System.err.println("jMusic RTMixer error: All instruments must have the same number of channels.");
System.exit(0);
}
temp[i] = lines[i - this.rtlines.length];
this.totLines += this.rtlines[i].getNumLines();
temp[i].setBufferSize(this.bufferSize);
}
this.rtlines = temp;
}
/**
* The controlChange method is called every time an instrument fills a
* sample buffer. This method is responsible for receiving the sample
* buffer and convolving it with the data in sampleArray.
*
* @param buffer a sample array filled by an instrument
* @param returned the number of samples in the buffer
* @param finished indicates whether the instruments current note is
* finished or not.
*/
public synchronized void controlChange(float[] buffer, int returned, boolean finished) {
for (int i = 0; i < returned; i++) {
sampleArray[i] += buffer[i];
}
if (++count == totLines) {
this.scorePosition += controlRate;
for (int j = 0; j < this.rtlines.length; j++) {
Instrument[] inst = rtlines[j].getInstrument();
for (int i = 0; i < inst.length; i++) {
inst[i].release();
}
}
count = 0;
this.writeOutAudio(sampleArray.length);
}
}
/**
* Begin starts RTMixer.
*/
public void begin() {
this.sampleArray = new float[bufferSize];
for (int i = 0; i < rtlines.length; i++) {
//System.out.println("RTMixer beginning " + i);
rtlines[i].start(this); //this.scorePosition,
}
}
/**
* Pauses RTMixer playback.
*/
public void pause() {
for (int i = 0; i < rtlines.length; i++) {
rtlines[i].pause();
}
}
/**
* Continues the RTMixer playback.
*/
public void unPause() {
for (int i = 0; i < rtlines.length; i++) {
rtlines[i].unPause();
}
}
/**
* Stops RTMixer playback.
*/
public void stop() {
for (int i = 0; i < rtlines.length; i++) {
rtlines[i].stop();
}
//this.finalize();
}
/**
* This method passes on external action requests (i.e. gui based action
* events) to each RTLine's externalAction method).
*
* @param obj an unspecified object type (externalAction will cast)
* @param actionNumber an indentifyer for the originator of the action
* request (i.e. if there are three buttons there would actionNumbers 1,2
* and 3)
*/
public void actionLines(Object obj, int actionNumber) {
for (int i = 0; i < rtlines.length; i++) {
rtlines[i].externalAction(obj, actionNumber);
}
//I need to develop some action listeners for this
}
//-----------------------------------------
//Private Methods
//-----------------------------------------
/**
* This method writes out the convolved sampleArray to the SourceDataLine
*
* @param length the number of samples to write
*/
private void writeOutAudio(final int length) {
bos.reset();
for (int i = 0; i < length; i++) {
// scale to avoid clipping
if (this.totLines > 1)
this.sampleArray[i] = this.sampleArray[i] / (this.totLines * 0.75f);
try {
dos.writeShort((short) (this.sampleArray[i] * 32767));
} catch (IOException ioe) {
ioe.printStackTrace();
}
this.sampleArray[i] = (float) 0.0;
}
int returned = this.dline.write(bos.toByteArray(), 0, bos.size());
this.currentTime += (long) length;
}
/**
* This method creates an instance of the JMF SourceDataLine object. This
* becomes the sink for all sample data leaving jMusic.
*
* @param bufferSize the size to set the SourceDataLine's buffer size to.
*/
private void initJMFSound(int bufferSize) {
//Set up jmf audio stuff
// AudioFormat af = new AudioFormat((float)this.sampleRate,16,
// this.channels,true,true);
AudioFormat af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
(float) this.sampleRate, 16, this.channels, this.channels * 2,
this.sampleRate, true);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
//System.out.println("Setting for audio line: "+info);
if (!AudioSystem.isLineSupported(info)) {
System.out.println(info);
System.err.println("jMusic RTMixer error: JMF Line not supported. " +
"Real time audio must be 16 bit stereo ... exiting .. sorry :(");
System.exit(1);
}
try {
this.dline = (SourceDataLine) AudioSystem.getLine(info);
//multiply buffersize by 2 because this is bytes not shorts
this.dline.open(af, bufferSize * 8); // 2 or 4?
this.dline.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void finalize() {
System.out.println("RTMixer finalizing...");
try {
dos.close();
bos.close();
} catch (IOException e) {
}
dline.stop();
dline.close();
}
}