why are longer waveforms louder?

Started by ouattwtym, July 10, 2011, 12:04:27

Previous topic - Next topic

ouattwtym

The code below plays six 440hz tones. Each tone is played using a longer wave form but also a faster frequency so the tone still comes out at 440hz. Now here is the bit that make no sense to me: each successive tone is louder than the one before (even though there is no reference to volume level per tone anywhere in the code). Can anyone explain this strange phenomenon?

package sandbox.lwjgl.demos.openal;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;

public class AlStrange {
	// originally from
	// http://lwjgl.org/documentation_openal_01.php
	
	static FloatBuffer createFloatBuffer(float... f) {
		FloatBuffer b = BufferUtils.createFloatBuffer(f.length);
		b.put(f);
		b.rewind();
		return b;
	}

	/** Buffers hold sound data. */
	int buffer;

	/** Sources are points emitting sound. */
	int source;

	/** Position of the source sound. */
	FloatBuffer sourcePos = createFloatBuffer(0.0f, 0.0f, 0.0f);

	/** Velocity of the source sound. */
	FloatBuffer sourceVel = createFloatBuffer(0.0f, 0.0f, 0.0f);

	/** Position of the listener. */
	FloatBuffer listenerPos = createFloatBuffer(0.0f, 0.0f, 0.0f);

	/** Velocity of the listener. */
	FloatBuffer listenerVel = createFloatBuffer(0.0f, 0.0f, 0.0f);

	/**
	 * Orientation of the listener. (first 3 elements are "at", second 3 are
	 * "up")
	 */
	FloatBuffer listenerOri = createFloatBuffer(0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);

	/**
	 * Throw if we've been given a bad name.
	 * 
	 * @param name
	 * @return Just passes through the given name for convenience.
	 */
	private int alCheckName(int name) {
		if (name <= 0) {
			throw new RuntimeException("Bad OpenAL name returned: " + name);
		}
		alCheck();
		return name;
	}

	/**
	 * Throw any <code>AL10.alGetError()</code>s that have happened.
	 */
	private void alCheck() {
		int e = AL10.alGetError();
		if (e != AL10.AL_NO_ERROR) {
			throw new RuntimeException("Unrecoverable OpenAL error code: " + e);
		}
	}

	private void play440tone(int waveLength) {
		int frequency = 440;
		int timeInSeconds = 2;

		byte[] waveForm = createSquareWave(waveLength);
		frequency *= waveLength;
		System.out.println("Frequency:" + frequency + " Wavelength:" + waveLength);

		int[] sample = new int[frequency * timeInSeconds];
		for (int x = 0; x < sample.length; x++) {
			sample[x] = waveForm[x % waveForm.length];
		}
		playWave(sample, frequency);
	}

	private byte[] createSquareWave(int waveLength) {
		byte[] answer = new byte[waveLength];
		for (int x = 0; x < waveLength; x++) {
			answer[x] = (byte) (x < (waveLength / 2) ? 0x3f : -0x3f);
		}
		return answer;
	}

	private void playWave(int[] z, int rate) {
		ByteBuffer bb = BufferUtils.createByteBuffer(z.length * 2);
		bb.order(ByteOrder.nativeOrder());
		ShortBuffer sb = bb.asShortBuffer();
		for (int x = 0; x < z.length; x++) {
			int s = z[x];
			s *= 256;
			sb.put((short) s);
		}

		buffer = alCheckName(AL10.alGenBuffers());
		AL10.alBufferData(buffer, AL10.AL_FORMAT_MONO16, bb, rate);

		// Bind the buffer with the source.
		source = alCheckName(AL10.alGenSources());
		AL10.alSourcei(source, AL10.AL_BUFFER, buffer);
		AL10.alSourcef(source, AL10.AL_PITCH, 1.0f);
		AL10.alSourcef(source, AL10.AL_GAIN, 1.0f);
		AL10.alSource(source, AL10.AL_POSITION, sourcePos);
		AL10.alSource(source, AL10.AL_VELOCITY, sourceVel);
		alCheck();

		setListenerValues();

		AL10.alSourcePlay(source);

		do {
			try {
				Thread.sleep(5);
			}
			catch (InterruptedException e) {
				// ignored
			}
		} while (AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING);
	}

	/**
	 * void setListenerValues()
	 * 
	 * We already defined certain values for the Listener, but we need to tell
	 * OpenAL to use that data. This function does just that.
	 */
	void setListenerValues() {
		AL10.alListener(AL10.AL_POSITION, listenerPos);
		AL10.alListener(AL10.AL_VELOCITY, listenerVel);
		AL10.alListener(AL10.AL_ORIENTATION, listenerOri);
	}

	/**
	 * void killALData()
	 * 
	 * We have allocated memory for our buffers and sources which needs to be
	 * returned to the system. This function frees that memory.
	 */
	void killALData() {
		AL10.alDeleteSources(source);
		AL10.alDeleteBuffers(buffer);
	}

	public void execute() {
		try {
			AL.create();
			alCheck();
		}
		catch (LWJGLException le) {
			throw new RuntimeException(le);
		}

		int waveLength = 2;
		for (int x = 0; x < 6; x++) {
			play440tone(waveLength);
			waveLength *= 2;
		}

		killALData();
		AL.destroy();
	}

	public static void main(String[] args) {
		new AlStrange().execute();
	}
}

CodeBunny

I'm not quite sure what you mean by "longer waveform," actually... Volume is actually the amplitude of the wave, and pitch is the wavelength. If you're playing tones at what I would expect to be called "longer waveform" (a longer wavelength), your pitch (and frequency) would be different, and it would sound lower.

But, when you say you're keeping the frequency the same, the only thing that's left is you amplifying the wave "height. On looking at your code, that's what it appears to be doing. So, yes, your code is perfectly functional, and you're making the base sounds louder.

Haven't you ever taken a physics course? These properties of sound are covered fairly early, usually right after electro-magnetism.

ouattwtym

I understand that the volume is the amplitude. My posting is not for want of basic physics knowledge. Probably for some other muppetry ;D. The last post said I am 'amplifying the wave height' but I do not think that is true. createSquareWave() makes a wave of constant amplitude, indeed the constants for the amplitude (0x3f and -0x3f) are in that method. The amplitude is constant so I expect the volume to be constant. It is not.

Ok - I think only two things are changing. I'll show it by example:

The first waveform { 63, -63 } is played at 880hz giving a 440hz tone.

Next a longer waveform { 63, 63, -63, -63 } played at 1760hz also giving a 440hz tone.

Next an even longer waveform { 63, 63, 63, 63, -63, -63, -63, -63 } is played at  3520hz also giving a 440hz tone.

etc.

The mystery is that each tone is louder than the last even though they plainly have the same amplitude (namely +/-63).

ouattwtym

I've put in a bit more debug-output and I quote the console output here:

Frequency : 880hz
Waveform  : 63,-63 (length=2)

Frequency : 1760hz
Waveform  : 63,63,-63,-63 (length=4)

Frequency : 3520hz
Waveform  : 63,63,63,63,-63,-63,-63,-63 (length=8)

Frequency : 7040hz
Waveform  : 63,63,63,63,63,63,63,63,-63,-63,-63,-63,-63,-63,-63,-63 (length=16)

Frequency : 14080hz
Waveform  : 63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63 (length=32)

Frequency : 28160hz
Waveform  : 63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63,-63 (length=64)


Six tones. Each sounds like International A (440hz) but I find it strange that each is louder.

CodeBunny

Hm. I apologize, I misunderstood your comment and your code - I now see what you mean. I think it would be a bit more clear if you said instead that you are increasing the resolution of your audio.

Perhaps making the wave more and more square makes it sound louder? Maybe you should experiment in audacity to see if what you're attempting to do is, in fact, what you're getting.

ouattwtym

No worries. For some reason ??? I've only previously used "resolution" in the context of computer graphics but I totally agree it would have been a perfect fit here and made my original post much clearer.

I came to a similar conclusion (that this was a broader computer-audio issue) and adapted my code to spit out wav files to get lwjgl/OpenAL out the way.

Here they are http://dl.dropbox.com/u/1702633/2011-07/tones110hz.zip . The first three don't play in foobar2k (too low frequency?) but the rest are okay.

As you say, maybe 2 samples isn't enough to say "this is a square wave". Perhaps you could interpolate between those two samples any old how. And, as you say, more samples could make it "more square". Interesting.

But 32 samples is pretty unambiguously super-super-square  :P  ... and yet the 64 sample .wav sounds different. I know we are no longer talking about an lwjgl/OpenAL issue so my post doesn't belong here but I'd really like to understand what is going on with this.