playing note on key hold using PyAudio and TK

642 views Asked by At

I wish to play a note on key hold using python. I am currently implementing and modifying the code found here. However, the problem I am facing is that the keydown function is called repeatedly when the key is held, resulting in the audio cutting in and out every time the keydown function is called.

I am looking for a way to maintain the ability to play chords, but remove this in-and out cutting by not calling keydown on the same key until keyup has been called on said key. The specific part of the code I have been working on is shown below.

Thanks in advance.

p = pyaudio.PyAudio()
chord = Chord()
gen = NoteGen()

def callback(in_data, frame_count, time_info, status):
    wave = chord(frame_count)
    data = wave.astype(numpy.float32).tostring()
    return (data, pyaudio.paContinue)

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

stream.start_stream()


def keydown(event):
    k = gen(event.char)
    print("add note: " + event.char)
    chord.add_note(k)
    print [(n.name, n.frequency) for n in chord.notes]


def keyup(event):
    k = gen(event.char)
    print("remove note: " + event.char)
    chord.remove_note(k)
    print [n.name for n in chord.notes]


root = Tk()
frame = Frame(root, width=100, height=100)
frame.bind_all("<KeyPress>", keydown)
frame.bind_all("<KeyRelease>", keyup)
frame.pack()
root.mainloop()

stream.stop_stream()
stream.close()
p.terminate()
2

There are 2 answers

0
Dalen On BEST ANSWER

Have a dictionary which will contain a key code as a key and a state (boolean for example) of that key as a value.

So, when key down is called, check if key code is in the dict, if it is not just add it with value 1. And start playing the note. If it is present, check the state of that key, and if it is pressed ignore the event. Else go to play the note. On key up set the state of the key to 0.

And, also, do not use callback to play audio, interrupting such played note will cause you nightmares.

Better write to a stream the note inside a thread.

Write the data in chunks and check for stopping flag.

When key up is called set the flag and do:

stream.stop_stream(); stream.start_stream()

To accurately cut off the sound.

Put writing part into try-except block so that when you force stream to stop from outside of the thread, nothing bad happens.

If you wish for a better way to use pyaudio for such things, which will allow you to play multiple sounds at once, search PyPI for SWmixer module. You'll also need numpy. Then you use SWMixer to load each sound into its own object and start and stop it as you want. Its usage is very similar to pygame.mixer module.

BTW, if you try to use multiple PyAudio streams parallelly, I am not sure how this would work.

I mean, I know that you can use one output and one input stream parallely without much trouble, but more than 2 output streams may cause some. That's why I mentioned SWMixer. I never tried using more than one, so I might be wrong.

But I did try using multiple instances of PyAudio itself. It works by diminishing the volume of previously called ones. So that my happen with streams too. So you will hear the last note louder than ones started before, or you might get funny noises out.

3
akarilimano On

You can use some global variable to check if a keydown event had been already fired.

key_pressed = False

def keydown(event):
    if key_pressed:
        return

    key_pressed = True
    k = gen(event.char)
    print("add note: " + event.char)
    chord.add_note(k)
    print [(n.name, n.frequency) for n in chord.notes]


def keyup(event):
    key_pressed = False
    k = gen(event.char)
    print("remove note: " + event.char)
    chord.remove_note(k)
    print [n.name for n in chord.notes]