Reputation: 3
I'm trying to learn a bit about audio programming, so I decided to see if I could figure out how to generate a sine wave and write it to a .wav file. From the reference here, I gather that each channel is simply interleaved in the data chunk at the end of the file. However, my generated wav file only seems to play in the left channel. I also compared my output to a true 440hz A and noticed that it is an octave lower than expected.
What I imagine is happening is that the programs I'm using to listen to the file are interpreting the data as a single channel and since each sample is duplicated once, playing the wave at 1/2 the intended frequency.
I know there is a Python module to assist with writing wav files, but this is an educational project, and I'm trying to learn about writing binary files and the wav file format.
Why can't I seem to get this file to play in two channels? Any help would be greatly appreciated.
I apologize for the messy code, I intend to refactor a bit once I get this last bug ironed out.
import struct
import math
# Frequency Table
A4 = 440.0
#Misc inputs
waveDuration = 3
amp_16 = 32760
# Wave Header
# Chunk Descriptor
chunkID = b'\x52\x49\x46\x46' #RIFF
hFormat = b'\x57\x41\x56\x45' #WAVE
# fmtSubChunk
fChunkID = b'\x66\x6d\x74\x20' #'fmt '
fChunkSize = 16 # 16 for PCM
audioFormat = 1 # 1 for PCM
numChannels = 2
sampleRate = 48000
bitsPerSample = 16
byteRate = int(sampleRate * numChannels * bitsPerSample / 8)
blockAlign = int(numChannels * bitsPerSample / 8)
# dataSubChunk
numSamples = waveDuration * sampleRate * numChannels
dChunkID = b'\x64\x61\x74\x61' #DATA
dChunkSize = int(numSamples * bitsPerSample / 8)
chunkSize = dChunkSize + 36 + 8
# Generate Sin Wave
def generateSin(sampleRate, bitDepth, frequency, amplitude, duration):
data = [0] * sampleRate * duration
angle = frequency * 2 * math.pi
cur_sample = 0
for sample in data:
data[cur_sample] = amplitude * (math.sin(angle * cur_sample/ sampleRate))
cur_sample += 1
return data
# Write .wav file
waveFile = open('test.wav', 'wb')
# Header
waveFile.write(chunkID)
waveFile.write(struct.pack('<i', chunkSize))
waveFile.write(hFormat)
# Format Subchunk
waveFile.write(fChunkID)
waveFile.write(struct.pack('<i', fChunkSize))
waveFile.write(struct.pack('<h', audioFormat))
waveFile.write(struct.pack('<h', numChannels))
waveFile.write(struct.pack('<i', sampleRate))
waveFile.write(struct.pack('<i', byteRate))
waveFile.write(struct.pack('<h', blockAlign))
waveFile.write(struct.pack('<h', bitsPerSample))
# Data Subchunk
waveFile.write(dChunkID)
waveFile.write(struct.pack('<i', dChunkSize))
for samp in generateSin(sampleRate, bitsPerSample, A4, amp_16, 1):
waveFile.write(struct.pack('<i', int(round(samp))))
waveFile.write(struct.pack('<i', int(round(samp))))
waveFile.close()
Upvotes: 0
Views: 727
Reputation: 6151
Great way for learning about wav
files.
Sample size of 16 bits means that each channel is 16 bits. You take the sample value of your sine wave, pack it into 4 bytes (32 bits) so you get something like XX00 where XX is the correct 16 bits value for the first channel, followed by 00 for the second channel, so the second channel has only zeros. After that - you write the same value again, so you duplicate the data of the previous sample, and that's why your frequency is wrong:
Change your for
loop from
for samp in generateSin(sampleRate, bitsPerSample, A4, amp_16, 1):
waveFile.write(struct.pack('<i', int(round(samp))))
waveFile.write(struct.pack('<i', int(round(samp))))
To -
for samp in generateSin(sampleRate, bitsPerSample, A4, amp_16, 1):
waveFile.write(struct.pack('<h', int(round(samp))))
waveFile.write(struct.pack('<h', int(round(samp))))
Now you will write only 2 bytes at a time, which is one channel, and then you will write the same value again, for the second channel. The frequency is not 440Hz but 500Hz - I believe that it is due to rounding the numbers.
Upvotes: 2