Martin
Martin

Reputation: 3385

Python PyAudio, output little cracky. Maybe math

Good day. I have a small problem that may be partly math.

The thing is I want to play Sine wave without fixed frequency. Therefore, not to make the sound cracky between transitions or during fixed frequency i need the sine wave to start and to end with amplitude zero. Mathematicly I understand what has to be done.

I chosed a way, where I adapt 'time' of the sine wave so it has time to finish all cycles. Basicly y=sin(2*pift) where f*t must be whole number.

The problem is that it actually works but not fully. All waves end up very near to zero, but not exactly there. Sound is quite ok while changing frequency but not perfect. I cant figure out why the last element cant land on zero.

If you would go through it and check i would be really greatful. Thx

            import pyaudio
            import numpy as np
            import matplotlib.pyplot as plt

            p = pyaudio.PyAudio()
            volume = 0.5     # range [0.0, 1.0]
            fs = 44100*4       # sampling rate, Hz, must be integer
            time = 0.1  # in seconds, may be float
            f = 400        # sine frequency, Hz, may be float
            k = np.arange(int(time*fs))
            t=np.arange(0,time,1/fs)
            start=0
            end=time




            stream = p.open(format=pyaudio.paFloat32,
                            channels=1,
                            rate=fs,
                            output=True)



            # generate samples, note conversion to float32 array
            for i in range(1000):

                start = 0
                end = 40 / f #time to acomplish whole whole cycles according to the give frequency - must be whole number

                print(len(t))
                t = np.arange(start, end, 1 / fs)
                samples = (np.sin(2*np.pi*f*t)).astype(np.float32)

                print(samples[0],samples[-1]) # The main problem. I need first and last elements in the sample to be zero.
                                            # Problem is that last element is only close to zero, which make the sound not so smooth

                #print(start+i,end+i)
                #print(samples)  # # # # # Shows first and last element

                f+=1

                # for paFloat32 sample values must be in range [-1.0, 1.0]


            # play. May repeat with different volume values (if done interactively)
                stream.write(volume*samples)

            stream.stop_stream()
            stream.close()

            p.terminate()

Upvotes: 1

Views: 574

Answers (1)

jaket
jaket

Reputation: 9341

The sine function repeats itself every multiple of 2*pi*N where N is a whole number. IOW, sin(2*pi) == sin(2*pi*2) == sin(2*pi*3) and so on.

The typical method for generating samples of a particular frequency is sin(2*pi*i*freq/sampleRate) where i is the sample number.

What follows is that the sine will only repeat at values of i such that i*freq/sampleRate is exactly equal to a whole number (I'm disregarding phase offsets).

The net result is that some frequency/sampleRate combinations may repeat after only a single cycle (1kHz @ 48kSr) whereas others may take a very long time to repeat (997Hz @ 48kSr).

It is not necessary that you change frequencies at exact zero crossings in order to avoid glitches. A better approach is this:

  1. Compute the phase increment for the desired frequency as phaseInc = 2*pi*freq/sampleRate
  2. For each output sample compute the output sample from the current phase. y = sin(phase)
  3. Update the phase by the phase increment: phase += phaseInc
  4. Repeat 2-3 for the desired number of samples.
  5. Goto step one to change freq

If you are insistent on changing at a zero crossing, just do it at the nearest sample where the phase crosses a multiple of 2*pi.

Upvotes: 1

Related Questions