Java raw audio output

sbrichards picture sbrichards · Oct 16, 2011 · Viewed 10.2k times · Source

Just wondering if there is a library in Java like the module PyAudiere in Python, that simply allows you to create tones and play them, like this sample Python code:

device = audiere.open_device()
tone = device.create_tone(500) #create a 500hz tone
tone.play()
tone.stop()

This simply assigns a variable to your default sound device, and then makes a tone for that device and plays it then stops it. Are there any libraries like this that are as simple to use?

I appreciate any and all feedback, thanks! :-D

Answer

Andrew Thompson picture Andrew Thompson · Oct 16, 2011

It is pretty simple to generate a sound in memory.

E.G.

Beeper

The important part of generating the tone (and storing it in a Clip) is encompassed in this code:

/** Generates a tone, and assigns it to the Clip. */
public void generateTone()
    throws LineUnavailableException {
    if ( clip!=null ) {
        clip.stop();
        clip.close();
    } else {
        clip = AudioSystem.getClip();
    }
    boolean addHarmonic = harmonic.isSelected();

    int intSR = ((Integer)sampleRate.getSelectedItem()).intValue();
    int intFPW = framesPerWavelength.getValue();

    float sampleRate = (float)intSR;

    // oddly, the sound does not loop well for less than
    // around 5 or so, wavelengths
    int wavelengths = 20;
    byte[] buf = new byte[2*intFPW*wavelengths];
    AudioFormat af = new AudioFormat(
        sampleRate,
        8,  // sample size in bits
        2,  // channels
        true,  // signed
        false  // bigendian
        );

    int maxVol = 127;
    for(int i=0; i<intFPW*wavelengths; i++){
        double angle = ((float)(i*2)/((float)intFPW))*(Math.PI);
        buf[i*2]=getByteValue(angle);
        if(addHarmonic) {
            buf[(i*2)+1]=getByteValue(2*angle);
        } else {
            buf[(i*2)+1] = buf[i*2];
        }
    }

    try {
        byte[] b = buf;
        AudioInputStream ais = new AudioInputStream(
            new ByteArrayInputStream(b),
            af,
            buf.length/2 );

        clip.open( ais );
    } catch(Exception e) {
        e.printStackTrace();
    }
}

Beeper.java

This is self contained code that shows the GUI seen above. It is a hybrid that will work either as a free floating frame, or embedded as an applet.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.text.DecimalFormat;

import javax.sound.sampled.*;

import java.io.ByteArrayInputStream;

/** Beeper presents a small, loopable tone that can be heard
by clicking on the Code Key.  It uses a Clip to loop the sound,
as well as for access to the Clip's gain control.
@author Andrew Thompson
@version 2009-12-19
@license LGPL */
public class Beeper extends JApplet {

    BeeperPanel bp;

    public void init() {
        bp = new BeeperPanel();
        getContentPane().add(bp);
        validate();

        String sampleRate = getParameter("samplerate");
        if (sampleRate!=null) {
            try {
                int sR = Integer.parseInt(sampleRate);
                bp.setSampleRate(sR);
            } catch(NumberFormatException useDefault) {
            }
        }

        String fpw = getParameter("fpw");
        if (fpw!=null) {
            try {
                int fPW = Integer.parseInt(fpw);
                JSlider slider = bp.getFramesPerWavelengthSlider();
                slider.setValue( fPW );
            } catch(NumberFormatException useDefault) {
            }
        }

        boolean harmonic = (getParameter("addharmonic")!=null);
        bp.setAddHarmonic(harmonic);

        bp.setUpSound();

        if ( getParameter("autoloop")!=null ) {
            String loopcount = getParameter("loopcount");
            if (loopcount!=null) {
                try {
                    Integer lC = Integer.parseInt(loopcount);
                    bp.loop( lC.intValue() );
                } catch(NumberFormatException doNotLoop) {
                }
            }
        }
    }

    public void stop() {
        bp.loopSound(false);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame f = new JFrame("Beeper");
                f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
                BeeperPanel BeeperPanel = new BeeperPanel();
                f.setContentPane(BeeperPanel);
                f.pack();
                f.setMinimumSize( f.getSize() );
                f.setLocationByPlatform(true);
                f.setVisible(true);
            }
        });
    }
}

/** The main UI of Beeper. */
class BeeperPanel extends JPanel {

    JComboBox sampleRate;
    JSlider framesPerWavelength;
    JLabel frequency;
    JCheckBox harmonic;
    Clip clip;

    DecimalFormat decimalFormat = new DecimalFormat("###00.00");

    BeeperPanel() {
        super(new BorderLayout());
        // Use current OS look and feel.
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
            SwingUtilities.updateComponentTreeUI(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
        setPreferredSize( new Dimension(300,300) );

        JPanel options = new JPanel();
        BoxLayout bl = new BoxLayout(options,BoxLayout.Y_AXIS);
        options.setLayout(bl);

        Integer[] rates = {
            new Integer(8000),
            new Integer(11025),
            new Integer(16000),
            new Integer(22050)
        };
        sampleRate = new JComboBox(rates);
        sampleRate.setToolTipText("Samples per second");
        sampleRate.setSelectedIndex(1);
        JPanel pSampleRate = new JPanel(new BorderLayout());
        pSampleRate.setBorder(new TitledBorder("Sample Rate"));
        pSampleRate.add( sampleRate );
        sampleRate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                setUpSound();
            }
        });
        options.add( pSampleRate );

        framesPerWavelength = new JSlider(JSlider.HORIZONTAL,10,200,25);
        framesPerWavelength.setPaintTicks(true);
        framesPerWavelength.setMajorTickSpacing(10);
        framesPerWavelength.setMinorTickSpacing(5);
        framesPerWavelength.setToolTipText("Frames per Wavelength");
        framesPerWavelength.addChangeListener( new ChangeListener(){
            public void stateChanged(ChangeEvent ce) {
                setUpSound();
            }
        } );

        JPanel pFPW = new JPanel( new BorderLayout() );
        pFPW.setBorder(new TitledBorder("Frames per Wavelength"));

        pFPW.add( framesPerWavelength );
        options.add( pFPW );

        JPanel bottomOption = new JPanel( new BorderLayout(4,4) );
        harmonic = new JCheckBox("Add Harmonic", false);
        harmonic.setToolTipText(
            "Add harmonic to second channel, one octave up");
        harmonic.addActionListener( new ActionListener(){
            public void actionPerformed(ActionEvent ae) {
                setUpSound();
            }
        } );
        bottomOption.add( harmonic, BorderLayout.WEST );

        frequency = new JLabel();
        bottomOption.add( frequency, BorderLayout.CENTER );

        options.add(bottomOption);

        add( options, BorderLayout.NORTH );

        JPanel play = new JPanel(new BorderLayout(3,3));
        play.setBorder( new EmptyBorder(4,4,4,4) );
        JButton bPlay  = new JButton("Code Key");
        bPlay.setToolTipText("Click to make tone!");
        Dimension preferredSize = bPlay.getPreferredSize();
        bPlay.setPreferredSize( new Dimension(
            (int)preferredSize.getWidth(),
            (int)preferredSize.getHeight()*3) );

        // TODO comment out to try KeyListener!
        //bPlay.setFocusable(false);
        bPlay.addKeyListener( new KeyAdapter(){
            @Override
            public void keyPressed(KeyEvent ke) {
                loopSound(true);
            }
        } );
        bPlay.addMouseListener( new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent me) {
                    loopSound(true);
                }

                @Override
                public void mouseReleased(MouseEvent me) {
                    loopSound(false);
                }
            } );
        play.add( bPlay );

        try {
            clip = AudioSystem.getClip();

            final FloatControl control = (FloatControl)
                clip.getControl( FloatControl.Type.MASTER_GAIN );

            final JSlider volume = new JSlider(
                JSlider.VERTICAL,
                (int)control.getMinimum(),
                (int)control.getMaximum(),
                (int)control.getValue()
                );
            volume.setToolTipText("Volume of beep");
            volume.addChangeListener( new ChangeListener(){
                public void stateChanged(ChangeEvent ce) {
                    control.setValue( volume.getValue() );
                }
            } );
            play.add( volume, BorderLayout.EAST );
        } catch(Exception e) {
            e.printStackTrace();
        }

        add(play, BorderLayout.CENTER);

        setUpSound();
    }

    public void loop(int loopcount) {
        if (clip!=null) {
            clip.loop( loopcount );
        }
    }

    public void setAddHarmonic(boolean addHarmonic) {
        harmonic.setSelected(addHarmonic);
    }

    /** Provides the slider for determining the # of frames per wavelength,
    primarily to allow easy adjustment by host classes. */
    public JSlider getFramesPerWavelengthSlider() {
        return framesPerWavelength;
    }

    /** Sets the sample rate to one of the four
    allowable rates. Is ignored otherwise. */
    public void setSampleRate(int sR) {
        switch (sR) {
            case 8000:
                sampleRate.setSelectedIndex(0);
                break;
            case 11025:
                sampleRate.setSelectedIndex(1);
                break;
            case 16000:
                sampleRate.setSelectedIndex(2);
                break;
            case 22050:
                sampleRate.setSelectedIndex(3);
                break;
            default:
        }
    }

    /** Sets label to current frequency settings. */
    public void setFrequencyLabel() {
        float freq = getFrequency();
        if (harmonic.isSelected()) {
            frequency.setText(
                decimalFormat.format(freq) +
                "(/" +
                decimalFormat.format(freq*2f) +
                ") Hz" );
        } else {
            frequency.setText( decimalFormat.format(freq) + " Hz" );
        }
    }

    /** Generate the tone and inform the user of settings. */
    public void setUpSound() {
        try {
            generateTone();
            setFrequencyLabel();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /** Provides the frequency at current settings for
    sample rate & frames per wavelength. */
    public float getFrequency() {
        Integer sR = (Integer)sampleRate.getSelectedItem();
        int intST = sR.intValue();
        int intFPW = framesPerWavelength.getValue();

        return (float)intST/(float)intFPW;
    }

    /** Loops the current Clip until a commence false is passed. */
    public void loopSound(boolean commence) {
        if ( commence ) {
            clip.setFramePosition(0);
            clip.loop( Clip.LOOP_CONTINUOUSLY );
        } else {
            clip.stop();
        }
    }

    /** Generates a tone, and assigns it to the Clip. */
    public void generateTone()
        throws LineUnavailableException {
        if ( clip!=null ) {
            clip.stop();
            clip.close();
        } else {
            clip = AudioSystem.getClip();
        }
        boolean addHarmonic = harmonic.isSelected();

        int intSR = ((Integer)sampleRate.getSelectedItem()).intValue();
        int intFPW = framesPerWavelength.getValue();

        float sampleRate = (float)intSR;

        // oddly, the sound does not loop well for less than
        // around 5 or so, wavelengths
        int wavelengths = 20;
        byte[] buf = new byte[2*intFPW*wavelengths];
        AudioFormat af = new AudioFormat(
            sampleRate,
            8,  // sample size in bits
            2,  // channels
            true,  // signed
            false  // bigendian
            );

        int maxVol = 127;
        for(int i=0; i<intFPW*wavelengths; i++){
            double angle = ((float)(i*2)/((float)intFPW))*(Math.PI);
            buf[i*2]=getByteValue(angle);
            if(addHarmonic) {
                buf[(i*2)+1]=getByteValue(2*angle);
            } else {
                buf[(i*2)+1] = buf[i*2];
            }
        }

        try {
            byte[] b = buf;
            AudioInputStream ais = new AudioInputStream(
                new ByteArrayInputStream(b),
                af,
                buf.length/2 );

            clip.open( ais );
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /** Provides the byte value for this point in the sinusoidal wave. */
    private static byte getByteValue(double angle) {
        int maxVol = 127;
        return (new Integer(
            (int)Math.round(
            Math.sin(angle)*maxVol))).
            byteValue();
    }
}