Wednesday, April 16, 2014

4. Writing a Wav File - 1

Here we will see how Python, using the built-in modules of struct and wave, writes a wave file. In contrast, in the next tutorial we discuss writing numpy arrays to wave files. Finally, in the tutorial after that, we use a music synthesis package csound, to do the same.




Any periodic signal is a sum of many sine and cosine waves. For the square wave, this link shows the Fourier components of the different frequencies.




This is the webpage.




At the end of the webpage, we can find the result.




Now we will use the first three Fourier coefficients to construct a C4 note signal. The C4 frequency is 261.626 Hz. The period of any wave is the inverse of frequency and in this case it is about 3.8 milliseconds. In a wave file, there are 44100 samples every second, and thus we can see how many samples form one period of wave.




The result is simply the product of the period (in seconds) and 44100 (samples per second). This gives a value of nearly 168. I could also have rounded off to 169. Even with 168, we see that the frequency is 262.5 Hz, which is close to the C4 frequency.




We need 168 samples according to this function.




This is the Python program to plot the first Fourier component.


# sin1.py
from math import sin,pi
import matplotlib.pyplot as plt
N=168 # period
x=range(N) # [0,1,2,...,167]
y=N*[0] # [0,0,0,.....,0]
for i in x:
  y[i]=4/pi*sin(2*pi*i/N)
y=3*y # 3 periods, y length is 3N
x=range(3*N) # [0,1,2,....,3*N-1]
plt.plot(x,y)
plt.show()  




The first two lines are import statements. Sometimes you will use from statements to import functions and data. Here we imported the sin function and pi data from the built-in math module. This means that from now on, we can refer to pi as just pi, and not math dot pi. We also imported the graphical library with an alias so we can access its functions and data with only plt and dot and then the relevant function or data.




Next we create three variables: one is integer N equal to one period, that is 168. x is a list from 0 to 167, and is created by the range() function. Finally we create the y variable. It has 168 elements and all are zero.




Finally in line 6 and 7 there is a block as can be seen from the collapse symbol which Notepad++ put. i is and index which iterates over all values in x, that is from 0 to 167, thus defining all 168 terms according to the mathematical formula.




In line 8, we duplicate the list three times so its size becomes 3N. For a periodic functions it is not necessary to make any more calculations since the numbers will be identical.




We next make the x coordinate go from 0 to 3N minus 1.




Finally this shows the plot of y over 3N values.




Next is the Python program for 3 Fourier coefficients. Now there are three sine terms with different frequencies and 3 different Fourier coefficients. It should be noted that there are no cosine terms in a square wave.


# sin3.py
from math import sin,pi
import matplotlib.pyplot as plt
N=168 # period
x=range(N) # [0,1,2,...,167]
y=N*[0] # [0,0,0,.....,0]
for i in x:
  y1=4/pi*sin(2*pi*i/N)
  y2=4/(3*pi)*sin(6*pi*i/N)
  y3=4/(5*pi)*sin(10*pi*i/N)
  y[i]=y1+y2+y3
y=3*y # 3 periods, y length is now 3*N
x=range(3*N) # [0,1,2,....,3*N-1]
plt.plot(x,y)
plt.show()  




This is the graph of the wave with 3 frequency components. It is more square-like. Note it is not between -1 and +1, but has over shoots.




We can calculate how many periods we need to stretch the sound to make a 5-second sound. We can see that 1313 periods are necessary for a wave file of that duration.




This is the second half of the program sin1313.py which is on pythonaudio.blogspot.com below this slide. We use the wave module to write a wave file. We need to enter four parameters, Line 16 through 19. For line 16, we can put 1, for mono and 2 for stereo. A large binary string is created using the packing function and it is written out using the writeframesraw() function. Then the file is closed, that is written.


# sin1313.py
from math import sin,pi
N=168 # period
x=range(N) # [0,1,2,...,167]
y=N*[0] # [0,0,0,.....,0]
for i in x:
  y1=4/pi*sin(2*pi*i/N)
  y2=4/(3*pi)*sin(6*pi*i/N)
  y3=4/(5*pi)*sin(10*pi*i/N)
  y[i]=y1+y2+y3
y=1313*y # 1313 periods, y length is now 1313*N

# save y to "sin1313.wav"
import wave
import struct
fout=wave.open("sin1313.wav","w")
fout.setnchannels(1) # Mono
fout.setsampwidth(2) # Sample is 2 Bytes
fout.setframerate(44100) # Sampling Frequency
fout.setcomptype('NONE','Not Compressed')
BinStr="" # Create a binary string of data
for i in range(len(y)):
    BinStr = BinStr + struct.pack('h',round(y[i]*20000))
fout.writeframesraw(BinStr)
fout.close()



This is the wave file opened in Audacity.




By selecting any large portion of audio or even the entire 5-seconds of audio and going to the Analyze menu option of Plot Spectrum, we get the frequency spectrum with 3 components.




Finally this is the program when y is square wave, that is either -1 or +1 at different times. The complete program is at pythonaudio.blogspot.com including the saving portion.


# sqwv.py
N=168 # period
x=range(N) # [0,1,2,...,167]
y=N*[0] # [0,0,0,.....,0]
for i in x:
  if i<N/2: y[i]=1.0
  else: y[i]=-1.0
      
y=1313*y # 1313 periods, y length is now 1313*N

# save y to "sqwv.wav"
import wave
import struct
fout=wave.open("sqwv.wav","w")
fout.setnchannels(1) # Mono
fout.setsampwidth(2) # Sample is 2 Bytes
fout.setframerate(44100) # Sampling Frequency
fout.setcomptype('NONE','Not Compressed')
BinStr="" # Create a binary string of data
for i in range(len(y)):
    BinStr = BinStr + struct.pack('h',round(y[i]*20000))
fout.writeframesraw(BinStr)
fout.close()



The resulting wave file is opened in Audacity. Actually you can open in any audio program.




This is the frequency components, showing the large number of components.




You may go to pythonaudio.blogspot.com to get the slides. To see a larger image of the slide, you can click on them at that page, which provides easy navigation controls. The text for the audio of the slides as well as any relevant source code is also on that page.



This is the video of Tutorial 4:


5 comments:

  1. in Python 3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul 5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)]


    File "C:/IPythonCode/sin1313.py", line 30, in
    BinStr = BinStr + struct.pack('h',round(y[i]*20000))

    TypeError: Can't convert 'bytes' object to str implicitly


    this is not working in new Python

    ReplyDelete
    Replies
    1. You can fix it by replacing that line:

      BinStr="" # Create a binary string of data

      with this line:

      BinStr=b'' # Create a binary string of data

      Delete
    2. Thank you so much for all the work and maintenance.

      Delete
  2. coin casino | Get $20 Free + 100 FS (Dec 2021)
    coin casino is 메리트 카지노 쿠폰 one of the best online gambling websites 메리트카지노 that offers $20 free to 인카지노 new players. It offers some great promotions, which are good for gambling

    ReplyDelete

About Me

I have used Python for the last 10+ years. I have a PhD in Electrical Engineering. I have taught Assembly Language programming of Intel-compatible chips as well as PC hardware interfacing. In my research, I have used Python to automate my calculations in physics and chemistry. I also use C++ and Java, often with Python.