RodrigoFranco RodrigoFranco - 1 month ago 12
Java Question

How to generate a .wav file with a sinusoidal wave and user-defined duration and frequency?

The following code should create a File containing a sine wave. At typical frequencies (220Hz, 440Hz, 880Hz) it goes great, but at many other frequencies it doesn't, for example take 225Hz, 883Hz and so on. What should I do to get a nice sinusoidal wave for any frequency?

import java.lang.Math;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class CreateSine
{
static String fileNameString = "Sine.wav";

static File file = new File(fileNameString);
static String filePath = file.getAbsolutePath();

static RandomAccessFile raw;

static int byteCount = 0;
static double pow215 = Math.pow(2, 15);

static float freq = 440.0f;
static int sRate = 44100;
static int bitDepth = 16;
static int nChannels = 1;
static int dur = 1;

static float changeRate = (float)((2.0 * Math.PI * freq) / sRate);

public static void main(String[] args)
{
try
{
raw = new RandomAccessFile(filePath, "rw");

raw.setLength(0); // Set file length to 0, to prevent unexpected behavior in case the file already existed
raw.writeBytes("RIFF");
raw.writeInt(0); // Final file size not known yet, write 0. This is = sample count + 36 bytes from header.
raw.writeBytes("WAVE");
raw.writeBytes("fmt ");
raw.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM
raw.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM
raw.writeShort(Short.reverseBytes((short)nChannels));// Number of channels, 1 for mono, 2 for stereo
raw.writeInt(Integer.reverseBytes(sRate)); // Sample rate
raw.writeInt(Integer.reverseBytes(sRate*bitDepth*nChannels/8)); // Byte rate, SampleRate*NumberOfChannels*bitDepth/8
raw.writeShort(Short.reverseBytes((short)(nChannels*bitDepth/8))); // Block align, NumberOfChannels*bitDepth/8
raw.writeShort(Short.reverseBytes((short)bitDepth)); // Bit Depth
raw.writeBytes("data");
raw.writeInt(0); // Data chunk size not known yet, write 0. This is = sample count.
}
catch(IOException e)
{
System.out.println("I/O exception occured while writing data");
}

for (int i = 0; i < sRate*dur; i++)
{
writeSample( (float)Math.sin( i * changeRate ) );
}

closeFile();
System.out.print("Finished");
}

static void writeSample(float floatValue)
{
try
{
short hexSample = (short)((floatValue * pow215));
raw.writeShort(Short.reverseBytes(hexSample));
byteCount += 2;
}
catch(IOException e)
{
System.out.println("I/O exception occured while writing data");
}
}

static void closeFile()
{
try
{
raw.seek(4); // Write size to RIFF header
raw.writeInt(Integer.reverseBytes(byteCount + 36));
raw.seek(40); // Write size to Subchunk2Size field
raw.writeInt(Integer.reverseBytes(byteCount));
raw.close();
}
catch(IOException e)
{
System.out.println("I/O exception occured while closing output file");
}
}
}


Thanks for your help.

Answer

Your question doesn't really describe what the problem is other than to say that it is not nice. I'm going to hazard a guess that you are getting clipping in your conversion from float to int.

  • The largest value the sin function can output is 1.0.
  • you multiply the output by 2^15, or 32768.
  • The largest positive signed short is 32767.

The reason you are experiencing clipping at different frequencies is that the sine function only hits 1.0 at sin(1+4k*pi/2), where k is any positive integer. Certain frequencies (e.g. 441Hz) will hit 1.0 very often and others will not.

The solution is to multiply the floating point numbers by ((2^15)-1)

Comments