0
$\begingroup$

I am trying to make a keyboard in python where every note's frequency is a ratio of the previous note but my output is strangely choppy for some reason. Here's an example of what I mean.

I'm using the sounddevice module and playing the sine wave through a callback function, here's what it looks like:

def callback(indata, outdata, frames, time, status):
    if status:
        print(status)

    global pressed
    if pressed:
        global pitched_sine
        global percent_thru_sine

        sine_len = len(pitched_sine)
        starting_index = int(sine_len * percent_thru_sine)
        out = np.concatenate((pitched_sine[starting_index:], pitched_sine[:starting_index - sine_len]))

        percent_thru_sine = (frames + int(percent_thru_sine * sine_len) + sine_len) % sine_len

        while percent_thru_sine > 1:
            percent_thru_sine -= 1

        out = np.resize(out,frames)
        out = np.reshape(out,(1,frames))
        outdata[:] = out.transpose()
    else:
        outdata[:] = None

I thought the choppiness was from the sine wave not starting where the previous one left off, like the end of one buffer having an amplitude of 1.0 immediately followed by the next buffer starting with 0.0, so I save the % through the last sine wave so I can start at that point in the new sine wave.

"pitched_sine" is generated when a keyboard button is pressed by pitch shifting a precalculated sine wave since I figured that calling np.sine() for every sample might have been causing a slowdown responsible for the hiccups in the output.

I also made sure that my pitch shift function or reshaping weren't resulting in weird results by making pyplots at every stage to check for strange looking sine waves but I didn't see any. Those were all the reasons I could think of, does anyone here have any ideas?

Here's the whole script if anyone wants to run it. If you do use the "-p" flag since the other stuff isn't relevant here. You'll also need this to be in the same folder and change the generated filename to "keys", press the spacebar while the program is running to play a note without its pitch changing.

EDIT: The chops correspond to either an "input overflow" or an "output underflow" even though the size of the output is always equal to the number of frames specified to the callback. According to this page I am using too much CPU time. Is there a way to optimize this?

EDIT 2: I figured it out, posted an answer but apparently I can't accept it for two days.

$\endgroup$
  • $\begingroup$ You may be clipping. Find out the maximum absolute value in your signal and make sure that it is compatible with whatever code is playing the sound. For example, Matlab's sound command wants values between -1 and 1, and will clip otherwise. This results in a choppy sound. $\endgroup$ – MBaz Feb 28 at 23:11
  • $\begingroup$ Good idea, but just I tested to see if it ever goes above 1.0 or below 1.0 and it never does. I then tried multiplying the whole output by 0.5 and it still occurs. I noticed that all of the hiccups correspond to an "input overflow" or "output underflow" according to the status, I am now looking into these on the documentation but don't fully understand them since the size of the output is always equal to the number of frames it asks for. $\endgroup$ – halbe Feb 28 at 23:32
  • $\begingroup$ Apparently the problem is that the callback is taking too much CPU time. Is there any way to optimize it? $\endgroup$ – halbe Feb 28 at 23:38
  • $\begingroup$ Yeah, that would explain it too. You can ask about optimizing the code in stackoverflow. One common technique, though, is to calculate and store several "frames" of data before you actually start to send them in. If the total input is not too long, this may avoid the overflows, but will increase your latency. $\endgroup$ – MBaz Mar 1 at 0:24
  • $\begingroup$ I discovered the problem, it actually wasn't the performance. The "percent_thru_sine" was calculating the index through the sine, I forgot to divide it by sine length to get the percentage. $\endgroup$ – halbe Mar 1 at 3:09
0
$\begingroup$

The "percent_thru_sine" line was actually calculating the index through the sine wave, I forgot to divide it by sine_len to make it a percentage.

percent_thru_sine = ((frames + int(percent_thru_sine * sine_len) + sine_len) % sine_len) / sine_len
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.