I have been testing various sine sweeps for use in impulse reverbs. I thought it would be useful to show the Java code for creating a short wave file with a sine sweep. The code is simple, but also interesting. It is interesting not only because it creates a sine sweep, but also because it shows the structure of simple wave files. Beginner DSP designers, who want to create or read wave files, may find it useful.
This code is not the most elegant, but it should be easily understood.
Random access files
Random access files are not needed for this little piece of code, but random-access implementations for wave files are a good idea. They allow skipping through the RIFF and wave chunks that are not needed. They also allow moving back and forth through the audio data in the wave data chunk, such as when fast forwarding or rewinding.
Wave data
Data in the wave file – audio data and format data – are stored with the least important byte first and most important byte last (little endian). Since there are no guarantees that all operating systems will store numbers with the appropriate byte order, we need a couple of functions to enforce the byte order.
Specifically, we need to be able to write four-byte integers and two-byte short integers. Simplistically, the code can be as follows.
public static void writeInt(RandomAccessFile file, long value) throws IOException
{
long b0 = value & 0x000000ff;
long b1 = (value & 0x0000ff00) >> 8;
long b2 = (value & 0x00ff0000) >> 16;
long b3 = (value & 0xff000000) >> 24;
file.writeByte((byte) b0);
file.writeByte((byte) b1);
file.writeByte((byte) b2);
file.writeByte((byte) b3);
}
public static void writeShort(RandomAccessFile file, int value) throws IOException
{
int b0 = value & 0x000000ff;
int b1 = (value & 0x0000ff00) >> 8;
file.writeByte((byte) b0);
file.writeByte((byte) b1);
}
Sine sweep
Below is the code to create a sine sweep. Here are the steps.
- We are designing a sine sweep that sweeps the frequencies between 50 Hz and 1000 Hz in 1 second of time and are placing the result in a wave file with the sampling rate 44.1 KHz and the sampling resolution 16 bits. Hence, we have the initial declarations.
- We begin by writing the RIFF identification "RIFF".
- We then write the size of the RIFF file. The size of the RIFF file includes: 1) 4 bytes for the RIFF sub-identification "WAVE"; 2) 4 bytes for the identification "fmt "; 3) 4 bytes for the size of the format chunk; 4) 16 bytes for the actual format chunk (the size of the format chunk is 16 bytes, since we do not count the sizes of the identification and the format chunk size); 5) 4 bytes for the identification "data"; and 6) samplingrate * channels * samplingresolution * T / 8 bytes for the audio data.
- We write the "WAVE" sub-identification to note that this RIFF file is a WAVE file.
- We write the identification of the format chunk.
- We write the size of the format chunk (less 4 bytes for the identification and 4 bytes for the size).
- We write various pieces of the format chunk. We will store PCM data and hence the compression code is 1. The rest are the number of channels, the sampling rate, the average bytes per second, the block align (size of sample for all channels combined), and the number of significant bits per sample (the sampling resolution).
- We write the identification of the data chunk.
- We write the audio data.
There are two types of sine sweeps here – a linear one (commented out) and an exponential one.
The value of the sine sweep at each audio sample will initially be between -1 and 1. We scale it up to two thirds of the range allowed by 16-bit recording (this 2/3 choice is random and can be changed). When writing the resulting values, we do not use writeShort, as writeShort is technically designed to write unsigned two-byte integers, whereas the audio data has a sign (they are positive or negative).
You can hear the resulting wave files in the wiki topic Sine sweep.
try
{
float f0 = 50;
float f1 = 1000;
float T = 1;
float samplingrate = 44100F;
int samplingresolution = 16;
int channels = 1;
// open a wave file
RandomAccessFile file = new RandomAccessFile("chirp.wav", "rw");
// write the header
file.seek(0);
file.writeBytes("RIFF");
writeInt(file, (int) (4 + 4 + 4 + 16 + 4 + samplingrate * channels *
samplingresolution * T / 8));
file.writeBytes("WAVE");
// write the format chunk
file.writeBytes("fmt ");
writeInt(file, 16L); // size of format chunk
writeShort(file, 1); // format type
writeShort(file, channels);
writeInt(file, (int) samplingrate);
writeInt(file, ((int) samplingrate * channels * samplingresolution) / 8);
writeShort(file, (channels * samplingresolution) / 8);
writeShort(file, samplingresolution);
// write the data chunk
file.writeBytes("data");
writeInt(file, (int) (samplingrate * channels * samplingresolution * T / 8));
// write the sine sweep data
for(int i = 0; i < samplingrate; i++)
{
double t = (double) i / samplingrate;
//double value = Math.sin(2D * Math.PI
* (f0 * t + (f1 - f0) * t * t / (2 * T)));
double value = Math.sin(2D * Math.PI * f0 * T
* (Math.pow(f1 / f0, t / T) - 1) / (Math.log(f1 / f0)));
int ivalue = (int) Math.min(Math.max(value * Short.MAX_VALUE * 2 / 3,
Short.MIN_VALUE), Short.MAX_VALUE);
file.writeByte((byte) (ivalue & 0xff));
file.writeByte((byte) (ivalue >>> 8 & 0xff));
}
file.close();
}
catch (IOException e)
{
System.out.println("Exception: " + e);
}
authors: mic
Add new comment