0

I have as input audio tones at various frequencies for varying lengths of time. I'm trying to slice those inputs into chunks (of the same size regardless of input frequency), and reproduce the chunks clearly in the output. When I use the blocking method, e.g. stream.write(), I get a clean output but there isn't much processing power left over to handle other tasks. So I've converted the code to a non-blocking approach with the callback() function, as shown below.

I've tried to pattern my code after this post: https://stackoverflow.com/a/22354593/12076263 but that does not work at all.

# Generate a 250 Hz tone in chunks amounting to 100 chunks per second,
# using a non-blocking method.
#
# How does one fit a 250 Hz wave into 100 Hz packets?
# Slice up a sine wave into segments of two-and-a-half waves apiece:
#
#               |              |              |              |
#  _     _     _|    _     _   | _     _     _|    _     _   | 
# / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   
#    \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/
#               |              |              |              |
#  <----X1----> | <----X2----> | <----X1----> | <----X2----> |
#              
# Play back each segment in alternating fashion.
# This simulates continuous audio where zero crossings do not necessarily
# occur at the edge of a chunk.

import numpy as np
import pyaudio
import time

# based on examples from https://people.csail.mit.edu/hubert/pyaudio/docs/
def callback(in_data, frame_count, time_info, status):
    waveData = Y.tobytes()
    return (waveData, pyaudio.paContinue)

Y = np.zeros((441), dtype = np.int16)   # initialize audio array Y
p = pyaudio.PyAudio()
stream = p.open(format = 8,             # 8 is code for int16 format
           channels = 1,
           rate = 44100,
           frames_per_buffer = 441,
           stream_callback = callback, # !! COMMENT OUT to enable blocking
           output = True)

stream.start_stream()

X1 = np.linspace(0, 5 * np.pi, num = 441, endpoint = False)
Y1 = (5000 * np.sin(X1)).astype(np.int16) # Generate two-and-a-half waves

X2 = np.linspace(5 * np.pi, 10 * np.pi, num = 441, endpoint = False)
Y2 = (5000 * np.sin(X2)).astype(np.int16) # The "other" two-and-a-half waves

# Play the two wave segments one after the other
# Result should be a clean 250 Hz tone
while True:
    Y = Y1                              # First set of waves
    time.sleep(0.0015)                  # This 1.5 millisecond delay
                                        #   simulates other stuff happening
                                        #   within the While loop.
    #stream.write(Y.tobytes())          # !! UNCOMMENT to use blocking method
    Y = Y2                              # Second set of waves
    time.sleep(0.0015)                  # More miscellaneous delays
    #stream.write(Y.tobytes())          # !! UNCOMMENT to use blocking method

Expected a distortion-free (as digital audio goes) sine wave, and got a heavily distorted sound, like the roar of a giant movie monster. There are normally no error messages, but if the time.sleep values are increased, underruns might occur. Also the problem goes away if the time.sleep statements are removed entirely, but the full application in which this will be used will have at least a 1.5 millisecond processing load per each chunk.

jeb.jr
  • 61
  • 9

3 Answers3

1

To partially answer my own question, the key is to use a queue to transfer data between the main loop and the callback function. Now the sound is not distorted. However, the blocking seems to have a significant impact on the execution time of each statement running in the While loop. Of course the overall loop slows down but it's looping at the correct refresh rate. I'd like to see the statements within the loop running at normal speed but they each run at about 1/5 of normal speed.

# Generate a 250 Hz tone in chunks amounting to 100 chunks per second,
# using a non-blocking method.
#
# How does one fit a 250 Hz wave into 100 Hz packets?
# Slice up a sine wave into segments of two-and-a-half waves apiece:
#
#               |              |              |              |
#  _     _     _|    _     _   | _     _     _|    _     _   | 
# / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   
#    \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/
#               |              |              |              |
#  <----X1----> | <----X2----> | <----X1----> | <----X2----> |
#              
# Play back each segment in alternating fashion.
# This simulates continuous audio where zero crossings do not necessarily
# occur at the edge of a chunk.

import numpy as np
import pyaudio
import time
import queue

# based on examples from https://people.csail.mit.edu/hubert/pyaudio/docs/
def callback(in_data, frame_count, time_info, status):
    Y = qu.get()
    waveData = Y.tobytes()
    return (waveData, pyaudio.paContinue)

qu = queue.Queue(maxsize = 1)           # 1 complete buffer (of length 441)
Y = np.zeros((441), dtype = np.int16)   # initialize audio array Y
p = pyaudio.PyAudio()
stream = p.open(format = 8,             # 8 is code for int16 format
           channels = 1,
           rate = 44100,
           frames_per_buffer = 441,
           stream_callback = callback,
           output = True)

stream.start_stream()

X1 = np.linspace(0, 5 * np.pi, num = 441, endpoint = False)
Y1 = (5000 * np.sin(X1)).astype(np.int16) # Generate two-and-a-half waves

X2 = np.linspace(5 * np.pi, 10 * np.pi, num = 441, endpoint = False)
Y2 = (5000 * np.sin(X2)).astype(np.int16) # The "other" two-and-a-half waves

# Play the two wave segments one after the other
# Result should be a clean 250 Hz tone
while True:
    qu.put(Y1)                          # First set of waves
    time.sleep(0.0015)                  # This 1.5 millisecond delay
                                        #   simulates other stuff happening
                                        #   within the While loop.
    qu.put(Y2)                              # Second set of waves
    time.sleep(0.0015)                  # More miscellaneous delays
jeb.jr
  • 61
  • 9
1

The other part of my question is how to speed up execution, because a lot of processing happens inside the while loop. However, something is slowing down the time-critical code by a factor of about 5. The 'while' section of code has been replaced with the code below. t2 minus t1 goes 5X faster if the qu.put statement is commented out, which is strange because the qu.put is outside the time-critical code measurement interval.

import time
from multiprocessing import Process

def whileLoop():
    while True:
        qu.put(Y1)
        t1 = time.time()
        # time-critical processing code goes here
        # must run in < 10ms to prevent audio underrun
        t2 = time.time()

        qu.put(Y2)
        t3 = time.time()
        # more time-critical processing code goes here
        # must run in < 10ms to prevent audio underrun
        t4 = time.time()

if __name__ == '__main__':
    pr = Process(target = whileLoop)
    pr.start()
    pr.join()
jeb.jr
  • 61
  • 9
0

You may need to define Y as global variable

Ras
  • 139
  • 4
  • 13