Reputation: 8958
I want to play a continuous sine wave (for now) which changes its frequency (for now) according to the mouse pointer position (for now). So, I generate and play a wave of the length of one frame (1/2s in the code below). The resulting sound has small gaps in the sound, enough to be very notable. Increasing the duration of the individual sounds does not help, changing the buffer size does not help, using dtick instead of dt does not help.
Is there a way to achieve a continuous sound this way? Or any other way?
Thank you
Versions: Python 3.9.3, pygame 2.0.1
Minimal (still lengthy) code:
import numpy as np
import pygame
from pygame import Color
from pygame.locals import KEYDOWN, KMOD_CTRL, QUIT, K_q
fade_out = np.linspace(1, 0, 100)
fade_in = np.linspace(0, 1, 100)
bits = 16
sample_rate = 44100
max_sample = 2 ** (bits - 1) - 1
def gen_sound(duration, freq):
n_samples = int(duration / 1000 * sample_rate)
buf = np.zeros((n_samples, 2), dtype=np.int16)
buf[:, 0] = (
0.25
* max_sample
* np.sin(2 * np.pi * freq[0] * np.arange(n_samples) / sample_rate)
)
buf[:, 1] = (
0.25
* max_sample
* np.sin(2 * np.pi * freq[1] * np.arange(n_samples) / sample_rate)
)
buf[-100:, 0] = (buf[-100:, 0] * fade_out).astype(int)
buf[-100:, 1] = (buf[-100:, 1] * fade_out).astype(int)
buf[:100, 0] = (buf[:100, 0] * fade_in).astype(int)
buf[:100, 1] = (buf[:100, 1] * fade_in).astype(int)
return pygame.sndarray.make_sound(buf)
def main(argv: list = None):
# initialize sound
pygame.mixer.pre_init(sample_rate, -bits, 2, 512)
# initialize pygame
pygame.init()
size = (1000, 800)
surf = pygame.display.set_mode(size)
surf.fill(Color("black"))
clock = pygame.time.Clock()
# frames per second and duration of a frame in ms
FPS = 2
dt = 1000 / FPS
dtick = 1000 / FPS
# position of the mouse pointer, None means outside of the window
mpos = None
# event loop
while True:
events = pygame.event.get()
for e in events:
if e.type == QUIT:
return
# shutdown with C-q
if e.type == KEYDOWN:
if e.mod & KMOD_CTRL and e.key == K_q:
return
if pygame.mouse.get_focused():
mpos = pygame.mouse.get_pos()
else:
mpos = None
freq = mpos if mpos else (440, 440)
sound = gen_sound(dt, freq)
sound.play(loops=0, fade_ms=0)
pygame.display.update()
dtick = clock.tick(FPS)
if __name__ == "__main__":
main()
Upvotes: 2
Views: 877
Reputation: 7876
You can directly modify the Sound
data while playing it, preventing the need to call .play()
every frame.
def gen_sound(buf, freq):
buf[:, 0] = (
0.25
* max_sample
* np.sin(2 * np.pi * freq[0] * np.arange(n_samples) / sample_rate)
)
buf[:, 1] = (
0.25
* max_sample
* np.sin(2 * np.pi * freq[1] * np.arange(n_samples) / sample_rate)
)
and in main:
def main(argv: list = None):
# ...
clock = pygame.time.Clock()
buf = np.zeros((n_samples, 2), dtype=np.int16)
sound = pygame.sndarray.make_sound(buf)
buf = pygame.sndarray.samples(sound) # This is required because make_sound copies the array, but we want a reference
gen_sound(buf, (440, 440))
sound.play(loops=-1, fade_ms=0)
# ...
# event loop
while True:
# ...
freq = mpos if mpos else (440, 440)
gen_sound(buf, freq)
pygame.display.update()
dtick = clock.tick(FPS)
Note that this will run into the problem desribed and solved here, e.g. sin-wave transitions aren't smooth. But that is out of scope of this question and my knowledge.
Upvotes: 2