简体   繁体   中英

How do I play a tone of a given Hz while a key is pressed AND stop playing it when it is released?

I found this code for playing sounds:

from array import array  # need a newline between normal text and code, edited to make it so
from time import sleep

import pygame
from pygame.mixer import Sound, get_init, pre_init

class Note(Sound):

    def __init__(self, frequency, volume=.1):
        self.frequency = frequency
        Sound.__init__(self, self.build_samples())
        self.set_volume(volume)

    def build_samples(self):
        period = int(round(get_init()[0] / self.frequency))
        samples = array("h", [0] * period)
        amplitude = 2 ** (abs(get_init()[1]) - 1) - 1
        for time in range(period):
            if time < period / 2:
                samples[time] = amplitude
            else:
                samples[time] = -amplitude
       return samples

if __name__ == "__main__":
    pre_init(44100, -16, 1, 1024)
    pygame.init()
    Note(440).play(-1)
sleep(5)

I use this method to for convenience:

def playSound(hz,ms):
    pre_init(44100, -16, 1, 1024)
    Note(hz).play(ms)

Putting -1 for ms plays the sound infinitely.

My problem is that I have no way of controlling when the sound stops. I have tried playing another inaudible frequency over it, but both can play at the same time. Any ideas on how to end the playing of the sound upon releasing the key?

You have to build "mainloop" to check keys periodically.

But system sends key events only to active window so you have to create window.

import pygame
import array

class Note(pygame.mixer.Sound):

    def __init__(self, frequency, volume=.1):
        self.frequency = frequency
        pygame.mixer.Sound.__init__(self, self.build_samples())
        self.set_volume(volume)

    def build_samples(self):
        period = int(round(pygame.mixer.get_init()[0] / self.frequency))
        samples = array.array("h", [0] * period)
        amplitude = 2 ** (abs(pygame.mixer.get_init()[1]) - 1) - 1
        for time in range(period):
            if time < period / 2:
                samples[time] = amplitude
            else:
                samples[time] = -amplitude
        return samples

if __name__ == "__main__":

    # --- init ---

    pygame.mixer.pre_init(44100, -16, 1, 1024)
    pygame.init()

    screen = pygame.display.set_mode((100, 100))

    # --- objects ---

    tones = {
        pygame.K_q: Note(440),
        pygame.K_w: Note(340),
        pygame.K_e: Note(540)
    }

    # --- mainloop ---

    running = True

    while running:
        for event in pygame.event.get():

            # closing window
            if event.type == pygame.QUIT:
                running = False

            # pressing key
            elif event.type == pygame.KEYDOWN:
                if event.key in tones:
                    print('press:', event.key)
                    tones[event.key].play(-1)

            # releasing key
            elif event.type == pygame.KEYUP:
                if event.key in tones:
                    print('release:', event.key)
                    tones[event.key].stop()

    # --- end ---

    pygame.quit()

EDIT: example which display pressed keys.

BTW: some keyboards don't send more then 4 pressed keys.

import pygame
import array

class Note(pygame.mixer.Sound):

    def __init__(self, frequency, volume=.1):
        self.frequency = frequency
        pygame.mixer.Sound.__init__(self, self.build_samples())
        self.set_volume(volume)

    def build_samples(self):
        period = int(round(pygame.mixer.get_init()[0] / self.frequency))
        samples = array.array("h", [0] * period)
        amplitude = 2 ** (abs(pygame.mixer.get_init()[1]) - 1) - 1
        for time in range(period):
            if time < period / 2:
                samples[time] = amplitude
            else:
                samples[time] = -amplitude
        return samples

if __name__ == "__main__":

    # --- init ---

    pygame.mixer.pre_init(44100, -16, 1, 1024)
    pygame.init()

    screen = pygame.display.set_mode((110, 100))

    # --- objects ---

    keys = [
        (pygame.K_q, 140),
        (pygame.K_w, 240),
        (pygame.K_e, 340),
        (pygame.K_r, 440),
        (pygame.K_t, 540),
    ]

    tones = {}
    pressed = {}

    for key, val in keys:
        tones[key] = Note(val)
        pressed[key] = False

    # --- mainloop ---

    running = True

    while running:

        # - events -

        for event in pygame.event.get():

            # closing window
            if event.type == pygame.QUIT:
                running = False

            # pressing key
            elif event.type == pygame.KEYDOWN:
                if event.key in tones:
                    #print('press:', event.key)
                    tones[event.key].play(-1)
                    pressed[event.key] = True

            # releasing key
            elif event.type == pygame.KEYUP:
                if event.key in tones:
                    #print('release:', event.key)
                    tones[event.key].stop()
                    pressed[event.key] = False

        # - draws -

        # clear screen
        screen.fill((0,0,0))
        # draw pressed keys
        for idx, key in enumerate(keys):
            if key[0] in pressed and pressed[key[0]]:
                x = 21*idx
                pygame.draw.rect(screen, (255,255,255), (x, 0, 20, 50))
        # send buffer on monitor
        pygame.display.flip()

    # --- end ---

    pygame.quit()

在此处输入图片说明

I asked on Cemetech and got this solution:

from array import array 
import pygame 
from pygame.mixer import Sound, get_init,  pre_init

class Note(pygame.mixer.Sound): 

    def __init__(self, frequency, volume=.1): 
        self.frequency = frequency 
        Sound.__init__(self, self.build_samples()) 
        self.set_volume(volume) 

    def build_samples(self): 
        period = int(round(get_init()[0] / self.frequency)) 
        samples = array("h", [0] * period) 
        amplitude = 2 ** (abs(get_init()[1]) - 1) - 1 
        for time in range(period): 
            if time < period / 2: 
                samples[time] = amplitude 
            else: 
                samples[time] = -amplitude 
        return samples  
pre_init(44100, -16, 1, 1024)
pygame.init() 
screen = pygame.display.set_mode([640, 480], 0) 

sounds = {} 
keymap = { 
    pygame.K_z: 880, 
    pygame.K_x: 440 
} 

while True: 
    evt = pygame.event.wait() 
    if evt.type == pygame.QUIT: 
        break 
    elif evt.type == pygame.KEYDOWN: 
        if evt.key in keymap: 
            note = Note(keymap[evt.key]) 
            note.play(-1) 
            sounds[evt.key] = note 
    elif evt.type == pygame.KEYUP: 
        if evt.key in sounds: 
            sounds.pop(evt.key).stop() 


pygame.quit()

It seems to work correctly and for multiple notes. Thanks everyone!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM