All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jm.audio.RTMixer Maven / Gradle / Ivy

The newest version!
/*



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(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy