music in 4k games

What are the options?

I have tried using midichannel.noteOn() but it makes the game very juddery as soon as the sysnthesizer.open() is called, even if it takes place in a separate thread. This is on 1.4, on 1.5 (jre ) it is still slow but no sound (no soundbank?).

If you don’t need anything fancy like different “instruments” you could write something that generates sine or square waves at the right frequency to play a little melody. If you make 2d arrays one deimension being the note and the other being the samples you can write the samples to an audio line based on looking up the note.

swpalmer: That sounds like a nice way to get some sounds, if you have the time could you please elaborate a bit more on it?

Generating a waveform in a buffer (array) & writing it directly to a line is probably the most space efficient way of getting sampled sound effects. However for music, you will need to keep checking the line & topping it up with the next note, which will use a bit more code. This also does not allow polyphonic sound (more than one note). To avoid clicks, each note must have attack & decay to zero.

I have had quite good results by generating an array of buffers with samples for each note across several octaves, then creating a buffer as long as the music track, then numerically adding the samples to it as required to make up the track. Create an AudioClip using the music track buffer & command it to loop indefinitely. SharpShooter16k used this technique. There is a slight difficulty when adding samples to the music track. This requires 16bit unsigned addition, but the buffer is in bytes. In SharpShooter I was already using NIO buffers for openGL & used those to map a byte buffer to a short buffer. For a 4k program, my best solution so far is to create the track using a normal int array, and then manually copy the values into a byte array.

You also need to store a set of note data to generate the track (frequency, length, start time). Currently mine is packed into an integer array, but this is very space inefficient. It would be better to encode this into a very long string. Must do this sometime :slight_smile: Oh… and you have to compose the tune of course. It helps to have some actual talent in this area ;D

Unfortunately, having done all the above uses over 500 compressed bytes; much more for a long track. So It hasn’t actually made it into any of my entries. Maybe soon, but I’m really really short on spare time :frowning:

Alan

Here’s the basic audio code that I’m using for bungie sound effects, altered to play “music”


import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.swing.JFrame;

/*
 * Created on 6-Jan-2006 by Scott Palmer
 *
 */

public class A extends JFrame implements Runnable {
	//static int threadCount;

	public static void main(String[] A) throws Exception {
		new A();
	}

	A() throws Exception {
		// start the music
		new Thread(this).start();

		do {
			while (true) {
				// get input
				;
				// compute stuff
				;
				// draw stuff
				;
				Thread.sleep(10);
			}
		} while (false);
	}

	public void run() {
		// Uncomment below for polyphonic sound.  Use voiceNumber
		// as an index to the sound track to play, or to create
		// a server thread for sound effects.

		// int voiceNumber = threadCount;
		// while(++threadCount < N)
		//     new Thread(this).start();

		try {
			final int a = 18000; // note amplitude
			final double z = Math.PI / 44000;
			// I'm using a frequency table for the notes here
			// algorithmic note generation is likely better
			final double f[] = { 261.63, // C4
					294.33, // D4
					327.03, // E4
					348.83, // F4
					392.44, // G4
					436.05, // A4
					490.55, // B4
			};

			SourceDataLine L = (SourceDataLine) AudioSystem
					.getLine(new DataLine.Info(SourceDataLine.class,
							new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
									44000, // 44kHz sampling freq
									16,// 16 bit samples
									1, // mono
									2, // 2 bytes per "frame"
									44000, // 1 frame/sample, so frame rate = sample rate
									true)));// big endian (works on Macs and Windows)
			L.open();
			L.start();
			// audio line is ready

			byte[] b = new byte[88000];
			while (true) {
				// here you would read the notes to play from a string or array
				int n = (int) (f.length * Math.random());
				// 22000 = 11000 16-bit samples.
				// Note duration is in 1/4 second units.
				int d = (int) (1 + 2 * Math.random());
				for (int t = 0; t < d * 22000;) {
					final short s = (short) (a * Math.sin(t * f[n] * z));
					// could add something to ensure that phase
					// ends cleanly to avoid pops between notes.
					b[t++] = (byte) (s >>> 8);
					b[t++] = (byte) (s & 0xFF);
				}
				L.write(b, 0, d * 22000);
			}
		} catch (Exception e) {
			//e.printStackTrace();
		}
	}
}

I just used a tiny midi file for sound and a single line of code to set it looping, but it still causes juddering. everything with java sound causes juddering!