javaaudiomidijavasoundjavax.sound.midi

Playing created midi sounds simultaneously in Java


I have a problem and I hope to get here an answer. I want to create many midi sounds without saving them on the client pc, because this should work in an Applet. The creating sound part isn't the problem, the problem is to play these sound simultaneously. I searched now more than 3 days for an asnwer, but couldn't find one that helped me.

Here is what i have at the moment:

My Sound (found part of it in an open-spurce project):

import java.io.*;
import javax.sound.sampled.*;
import java.net.URL;
import javax.sound.*;

public class Sound {

    /**
     *  The sample rate - 44,100 Hz for CD quality audio.
     */
    public static int channels = 2;




    public static final int SAMPLE_RATE = 44100;

    private static final int BYTES_PER_SAMPLE = 4;                // 16-bit audio
    private static final int BITS_PER_SAMPLE = 16;                // 16-bit audio
    private static final double MAX_16_BIT = Short.MAX_VALUE;     // 32,767
    private static final int SAMPLE_BUFFER_SIZE = 4096;


    static SourceDataLine[] line = new SourceDataLine[channels];    // to play the         sound
    private static byte[][] buffer = new byte[2][];         // our internal buffer
    private static int i = 0;             // number of samples currently in internal buffer
    private static int i2 = 0;

    public static Mixer mainmix;

    // static initializer
   public Sound()
   {
       init();
   }

    // open up an audio stram
    static void init() {
        try {

            Mixer.Info[] mixerinfo = AudioSystem.getMixerInfo();    

            mainmix = AudioSystem.getMixer(mixerinfo[0]);

            Line.Info lineinf[] = mainmix.getSourceLineInfo();

            // 44,100 samples per second, 16-bit audio, mono, signed PCM, little Endian
            AudioFormat format = new AudioFormat((float) SAMPLE_RATE, BITS_PER_SAMPLE, 2, true, false);
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);


            line[0] = (SourceDataLine) mainmix.getLine(info);
            line[1] = (SourceDataLine) mainmix.getLine(info);
            //line[2] = (SourceDataLine) mainmix.getLine(info);

            Control[] linectrl = line[0].getControls();
            Control[] linectrl1 = line[1].getControls();



            //line = (SourceDataLine) AudioSystem.getLine(info);
            line[0].open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);
            line[1].open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);
           // line[2].open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);
            //line[1].open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);



            // the internal buffer is a fraction of the actual buffer size, this choice is arbitrary
            // it gets diveded because we can't expect the buffered data to line up exactly with when
            // the sound card decides to push out its samples.
            buffer[0] = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE];
            buffer[1] = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE];
            //buffer[2] = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE];
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.exit(1);
    }


        // no sound gets made before this call
        line[0].flush();
        line[0].start();
        line[1].flush();
        line[1].start();
}

    /**
     * Close standard audio.
     */
    public final void close() {
        line[0].drain();
        line[0].stop();

       line[1].drain();
       line[1].stop();        
    }

    public final void play(double in) {

        // clip if outside [-1, +1]
        if (in < -1.0) in = -1.0;
        if (in > +1.0) in = +1.0;

        // convert to bytes
        short s = (short) (MAX_16_BIT * in);
        buffer[0][i++] = (byte) s;
        buffer[0][i++] = (byte) (s >> 8);   // little Endian
        buffer[0][i++] = (byte) s;
        buffer[0][i++] = (byte) (s >> 8);   // little Endian


        // send to sound card if buffer is full        
        if (i >= buffer.length) {
            line[0].write(buffer[0], 0, 4);

            i = 0;
        }
    }

    public void play(double[] input) {
        for (int i = 0; i < input.length; i++) {
            play(input[i]);

        }
    }

    public final void play(double in, double an) {


        // clip if outside [-1, +1]
        if (in < -1.0) in = -1.0;
        if (in > +1.0) in = +1.0;

        // convert to bytes
        short s = (short) (MAX_16_BIT * in);
        buffer[1][i++] = (byte) s;
        buffer[1][i++] = (byte) (s >> 8);   // little Endian
        buffer[1][i++] = (byte) s;
        buffer[1][i++] = (byte) (s >> 8);   // little Endian

        // clip if outside [-1, +1]
        if (an < -1.0) an = -1.0;
        if (an > +1.0) an = +1.0;

        // convert to bytes
        short a = (short) (MAX_16_BIT * an);
        buffer[0][i2++] = (byte) a;
        buffer[0][i2++] = (byte) (a >> 8);   // little Endian
        buffer[0][i2++] = (byte) a;
        buffer[0][i2++] = (byte) (a >> 8);   // little Endian

        // send to sound card if buffer is full        
        if (i >= buffer.length) {

            line[1].write(buffer[1], 0, 4);
            line[0].write(buffer[0], 0, 4);


            i2 = 0;
            i = 0;
        }
    }

 public void play(double[] input,double[] input1) {
        for (int i = 0; i < input.length; i++) {
            play(input[i],input1[i]);

        }
    }
}

This is where i create my sound:

    public class Note {
    private final int SAMPLE_RATE = 44100;
    private double[] tone;
    public Note(double hz, double duration, double amplitude)
    {
        int N = (int) Math.round(SAMPLE_RATE * duration);
        tone = new double[N+1];
        for (int i = 0; i <= N; i++)
            tone[i] = amplitude * Math.sin(2 * Math.PI * i * hz / SAMPLE_RATE);
    }

    public double[] getTone()
    {
        return tone;
    }

}

Here my Temporary Main class: public class test {

    public static void main(String[] args) {
        Sound sound = new Sound();

        //Note(hz (freg),seconds (duration),vol(amplitude))
        Note note1 = new Note(50.0,3.0,6);
        Note note2 = new Note(10.0,3.0,6);
        sound.play(note1.getTone(),note2.getTone());
    }

}   

I thought about Threads but I don't think if I have 20-30++ Thread, that won't be simultaneously and the perfomance wouldn't be that good. I saw examples were they played sounds simultaneously, but then they loaded the sounds from a file and when I tried it with midi it didn't work.


Solution

  • I don't see where you are using a timestamp. With MIDI, one can usually either schedule a sound for immediate playback, or for playback at a given moment. I think to play simultaneous sounds in MIDI, it is usually just a matter of scheduling all the events for the same time or timestamp.

    The tutorial section that deals with this is here: http://docs.oracle.com/javase/tutorial/sound/MIDI-messages.html

    Have you written your own playback code? Can you revise it to play back at a given point in time?

    Also, it looks like you will have to mix together the sounds yourself if you are writing your own. This is mostly just a matter of adding the data from each playback event while it is in PCM form. Forgive me for not taking the time to figure out where in your code you can or should do this. Here is a quick summary of the needed steps: make a while loop that iterates through each event scheduled for the given moment, then assemble the byte stream into PCM data for each frame and sum the simultaneously playing frames together (left channel data with left, right channel data with right), then disassemble the PCM value back to bytes in the format needed by the SourceDataLine.