import sys import os import unittest import pathlib import platform import time from pygame.tests.test_utils import example_path import pygame from pygame import mixer IS_PYPY = "PyPy" == platform.python_implementation() ################################### CONSTANTS ################################## FREQUENCIES = [11025, 22050, 44100, 48000] SIZES = [-16, -8, 8, 16] # fixme # size 32 failed in test_get_init__returns_exact_values_used_for_init CHANNELS = [1, 2] BUFFERS = [3024] CONFIGS = [ {"frequency": f, "size": s, "channels": c} for f in FREQUENCIES for s in SIZES for c in CHANNELS ] # Using all CONFIGS fails on a Mac; probably older SDL_mixer; we could do: # if platform.system() == 'Darwin': # But using all CONFIGS is very slow (> 10 sec for example) # And probably, we don't need to be so exhaustive, hence: CONFIG = {"frequency": 44100, "size": 32, "channels": 2, "allowedchanges": 0} class InvalidBool: """To help test invalid bool values.""" __bool__ = None ############################## MODULE LEVEL TESTS ############################# class MixerModuleTest(unittest.TestCase): def tearDown(self): mixer.quit() mixer.pre_init(0, 0, 0, 0) def test_init__keyword_args(self): # note: this test used to loop over all CONFIGS, but it's very slow.. mixer.init(**CONFIG) mixer_conf = mixer.get_init() self.assertEqual(mixer_conf[0], CONFIG["frequency"]) # Not all "sizes" are supported on all systems, hence "abs". self.assertEqual(abs(mixer_conf[1]), abs(CONFIG["size"])) self.assertGreaterEqual(mixer_conf[2], CONFIG["channels"]) def test_pre_init__keyword_args(self): # note: this test used to loop over all CONFIGS, but it's very slow.. mixer.pre_init(**CONFIG) mixer.init() mixer_conf = mixer.get_init() self.assertEqual(mixer_conf[0], CONFIG["frequency"]) # Not all "sizes" are supported on all systems, hence "abs". self.assertEqual(abs(mixer_conf[1]), abs(CONFIG["size"])) self.assertGreaterEqual(mixer_conf[2], CONFIG["channels"]) def test_pre_init__zero_values(self): # Ensure that argument values of 0 are replaced with # default values. No way to check buffer size though. mixer.pre_init(22050, -8, 1) # Non default values mixer.pre_init(0, 0, 0) # Should reset to default values mixer.init(allowedchanges=0) self.assertEqual(mixer.get_init()[0], 44100) self.assertEqual(mixer.get_init()[1], -16) self.assertGreaterEqual(mixer.get_init()[2], 2) def test_init__zero_values(self): # Ensure that argument values of 0 are replaced with # preset values. No way to check buffer size though. mixer.pre_init(44100, 8, 1, allowedchanges=0) # None default values mixer.init(0, 0, 0) self.assertEqual(mixer.get_init(), (44100, 8, 1)) # def test_get_init__returns_exact_values_used_for_init(self): # # TODO: size 32 fails in this test (maybe SDL_mixer bug) # # TODO: 2) When you start the mixer, you request the settings. # # But it can be that a sound system doesn’t support what you request… # # and it gives you back something close to what you request but not equal. # # So, you can’t test for equality. # # See allowedchanges # for init_conf in CONFIGS: # frequency, size, channels = init_conf.values() # if (frequency, size) == (22050, 16): # continue # mixer.init(frequency, size, channels) # mixer_conf = mixer.get_init() # self.assertEqual(tuple(init_conf.values()), mixer_conf) # mixer.quit() def test_get_init__returns_None_if_mixer_not_initialized(self): self.assertIsNone(mixer.get_init()) def test_get_num_channels__defaults_eight_after_init(self): mixer.init() self.assertEqual(mixer.get_num_channels(), 8) def test_set_num_channels(self): mixer.init() default_num_channels = mixer.get_num_channels() for i in range(1, default_num_channels + 1): mixer.set_num_channels(i) self.assertEqual(mixer.get_num_channels(), i) def test_quit(self): """get_num_channels() Should throw pygame.error if uninitialized after mixer.quit()""" mixer.init() mixer.quit() self.assertRaises(pygame.error, mixer.get_num_channels) # TODO: FIXME: appveyor and pypy (on linux) fails here sometimes. @unittest.skipIf(sys.platform.startswith("win"), "See github issue 892.") @unittest.skipIf(IS_PYPY, "random errors here with pypy") def test_sound_args(self): def get_bytes(snd): return snd.get_raw() mixer.init() sample = b"\x00\xff" * 24 wave_path = example_path(os.path.join("data", "house_lo.wav")) uwave_path = str(wave_path) bwave_path = uwave_path.encode(sys.getfilesystemencoding()) snd = mixer.Sound(file=wave_path) self.assertTrue(snd.get_length() > 0.5) snd_bytes = get_bytes(snd) self.assertTrue(len(snd_bytes) > 1000) self.assertEqual(get_bytes(mixer.Sound(wave_path)), snd_bytes) self.assertEqual(get_bytes(mixer.Sound(file=uwave_path)), snd_bytes) self.assertEqual(get_bytes(mixer.Sound(uwave_path)), snd_bytes) arg_emsg = "Sound takes either 1 positional or 1 keyword argument" with self.assertRaises(TypeError) as cm: mixer.Sound() self.assertEqual(str(cm.exception), arg_emsg) with self.assertRaises(TypeError) as cm: mixer.Sound(wave_path, buffer=sample) self.assertEqual(str(cm.exception), arg_emsg) with self.assertRaises(TypeError) as cm: mixer.Sound(sample, file=wave_path) self.assertEqual(str(cm.exception), arg_emsg) with self.assertRaises(TypeError) as cm: mixer.Sound(buffer=sample, file=wave_path) self.assertEqual(str(cm.exception), arg_emsg) with self.assertRaises(TypeError) as cm: mixer.Sound(foobar=sample) self.assertEqual(str(cm.exception), "Unrecognized keyword argument 'foobar'") snd = mixer.Sound(wave_path, **{}) self.assertEqual(get_bytes(snd), snd_bytes) snd = mixer.Sound(*[], **{"file": wave_path}) with self.assertRaises(TypeError) as cm: mixer.Sound([]) self.assertEqual(str(cm.exception), "Unrecognized argument (type list)") with self.assertRaises(TypeError) as cm: snd = mixer.Sound(buffer=[]) emsg = "Expected object with buffer interface: got a list" self.assertEqual(str(cm.exception), emsg) ufake_path = "12345678" self.assertRaises(IOError, mixer.Sound, ufake_path) self.assertRaises(IOError, mixer.Sound, "12345678") with self.assertRaises(TypeError) as cm: mixer.Sound(buffer="something") emsg = "Unicode object not allowed as buffer object" self.assertEqual(str(cm.exception), emsg) self.assertEqual(get_bytes(mixer.Sound(buffer=sample)), sample) if type(sample) != str: somebytes = get_bytes(mixer.Sound(sample)) # on python 2 we do not allow using string except as file name. self.assertEqual(somebytes, sample) self.assertEqual(get_bytes(mixer.Sound(file=bwave_path)), snd_bytes) self.assertEqual(get_bytes(mixer.Sound(bwave_path)), snd_bytes) snd = mixer.Sound(wave_path) with self.assertRaises(TypeError) as cm: mixer.Sound(wave_path, array=snd) self.assertEqual(str(cm.exception), arg_emsg) with self.assertRaises(TypeError) as cm: mixer.Sound(buffer=sample, array=snd) self.assertEqual(str(cm.exception), arg_emsg) snd2 = mixer.Sound(array=snd) self.assertEqual(snd.get_raw(), snd2.get_raw()) def test_sound_unicode(self): """test non-ASCII unicode path""" mixer.init() import shutil ep = example_path("data") temp_file = os.path.join(ep, "你好.wav") org_file = os.path.join(ep, "house_lo.wav") shutil.copy(org_file, temp_file) try: with open(temp_file, "rb") as f: pass except OSError: raise unittest.SkipTest("the path cannot be opened") try: sound = mixer.Sound(temp_file) del sound finally: os.remove(temp_file) @unittest.skipIf( os.environ.get("SDL_AUDIODRIVER") == "disk", "this test fails without real sound card", ) def test_array_keyword(self): try: from numpy import ( array, arange, zeros, int8, uint8, int16, uint16, int32, uint32, ) except ImportError: self.skipTest("requires numpy") freq = 22050 format_list = [-8, 8, -16, 16] channels_list = [1, 2] a_lists = {f: [] for f in format_list} a32u_mono = arange(0, 256, 1, uint32) a16u_mono = a32u_mono.astype(uint16) a8u_mono = a32u_mono.astype(uint8) au_list_mono = [(1, a) for a in [a8u_mono, a16u_mono, a32u_mono]] for format in format_list: if format > 0: a_lists[format].extend(au_list_mono) a32s_mono = arange(-128, 128, 1, int32) a16s_mono = a32s_mono.astype(int16) a8s_mono = a32s_mono.astype(int8) as_list_mono = [(1, a) for a in [a8s_mono, a16s_mono, a32s_mono]] for format in format_list: if format < 0: a_lists[format].extend(as_list_mono) a32u_stereo = zeros([a32u_mono.shape[0], 2], uint32) a32u_stereo[:, 0] = a32u_mono a32u_stereo[:, 1] = 255 - a32u_mono a16u_stereo = a32u_stereo.astype(uint16) a8u_stereo = a32u_stereo.astype(uint8) au_list_stereo = [(2, a) for a in [a8u_stereo, a16u_stereo, a32u_stereo]] for format in format_list: if format > 0: a_lists[format].extend(au_list_stereo) a32s_stereo = zeros([a32s_mono.shape[0], 2], int32) a32s_stereo[:, 0] = a32s_mono a32s_stereo[:, 1] = -1 - a32s_mono a16s_stereo = a32s_stereo.astype(int16) a8s_stereo = a32s_stereo.astype(int8) as_list_stereo = [(2, a) for a in [a8s_stereo, a16s_stereo, a32s_stereo]] for format in format_list: if format < 0: a_lists[format].extend(as_list_stereo) for format in format_list: for channels in channels_list: try: mixer.init(freq, format, channels) except pygame.error: # Some formats (e.g. 16) may not be supported. continue try: __, f, c = mixer.get_init() if f != format or c != channels: # Some formats (e.g. -8) may not be supported. continue for c, a in a_lists[format]: self._test_array_argument(format, a, c == channels) finally: mixer.quit() def _test_array_argument(self, format, a, test_pass): from numpy import array, all as all_ try: snd = mixer.Sound(array=a) except ValueError: if not test_pass: return self.fail("Raised ValueError: Format %i, dtype %s" % (format, a.dtype)) if not test_pass: self.fail( "Did not raise ValueError: Format %i, dtype %s" % (format, a.dtype) ) a2 = array(snd) a3 = a.astype(a2.dtype) lshift = abs(format) - 8 * a.itemsize if lshift >= 0: # This is asymmetric with respect to downcasting. a3 <<= lshift self.assertTrue(all_(a2 == a3), "Format %i, dtype %s" % (format, a.dtype)) def _test_array_interface_fail(self, a): self.assertRaises(ValueError, mixer.Sound, array=a) def test_array_interface(self): mixer.init(22050, -16, 1, allowedchanges=0) snd = mixer.Sound(buffer=b"\x00\x7f" * 20) d = snd.__array_interface__ self.assertTrue(isinstance(d, dict)) if pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN: typestr = "") if is_lil_endian else (">", "<") shape = (10, channels)[:ndim] strides = (channels * itemsize, itemsize)[2 - ndim :] exp = Exporter(shape, format=frev + "i") snd = mixer.Sound(array=exp) buflen = len(exp) * itemsize * channels imp = Importer(snd, buftools.PyBUF_SIMPLE) self.assertEqual(imp.ndim, 0) self.assertTrue(imp.format is None) self.assertEqual(imp.len, buflen) self.assertEqual(imp.itemsize, itemsize) self.assertTrue(imp.shape is None) self.assertTrue(imp.strides is None) self.assertTrue(imp.suboffsets is None) self.assertFalse(imp.readonly) self.assertEqual(imp.buf, snd._samples_address) imp = Importer(snd, buftools.PyBUF_WRITABLE) self.assertEqual(imp.ndim, 0) self.assertTrue(imp.format is None) self.assertEqual(imp.len, buflen) self.assertEqual(imp.itemsize, itemsize) self.assertTrue(imp.shape is None) self.assertTrue(imp.strides is None) self.assertTrue(imp.suboffsets is None) self.assertFalse(imp.readonly) self.assertEqual(imp.buf, snd._samples_address) imp = Importer(snd, buftools.PyBUF_FORMAT) self.assertEqual(imp.ndim, 0) self.assertEqual(imp.format, format) self.assertEqual(imp.len, buflen) self.assertEqual(imp.itemsize, itemsize) self.assertTrue(imp.shape is None) self.assertTrue(imp.strides is None) self.assertTrue(imp.suboffsets is None) self.assertFalse(imp.readonly) self.assertEqual(imp.buf, snd._samples_address) imp = Importer(snd, buftools.PyBUF_ND) self.assertEqual(imp.ndim, ndim) self.assertTrue(imp.format is None) self.assertEqual(imp.len, buflen) self.assertEqual(imp.itemsize, itemsize) self.assertEqual(imp.shape, shape) self.assertTrue(imp.strides is None) self.assertTrue(imp.suboffsets is None) self.assertFalse(imp.readonly) self.assertEqual(imp.buf, snd._samples_address) imp = Importer(snd, buftools.PyBUF_STRIDES) self.assertEqual(imp.ndim, ndim) self.assertTrue(imp.format is None) self.assertEqual(imp.len, buflen) self.assertEqual(imp.itemsize, itemsize) self.assertEqual(imp.shape, shape) self.assertEqual(imp.strides, strides) self.assertTrue(imp.suboffsets is None) self.assertFalse(imp.readonly) self.assertEqual(imp.buf, snd._samples_address) imp = Importer(snd, buftools.PyBUF_FULL_RO) self.assertEqual(imp.ndim, ndim) self.assertEqual(imp.format, format) self.assertEqual(imp.len, buflen) self.assertEqual(imp.itemsize, 2) self.assertEqual(imp.shape, shape) self.assertEqual(imp.strides, strides) self.assertTrue(imp.suboffsets is None) self.assertFalse(imp.readonly) self.assertEqual(imp.buf, snd._samples_address) imp = Importer(snd, buftools.PyBUF_FULL_RO) self.assertEqual(imp.ndim, ndim) self.assertEqual(imp.format, format) self.assertEqual(imp.len, buflen) self.assertEqual(imp.itemsize, itemsize) self.assertEqual(imp.shape, exp.shape) self.assertEqual(imp.strides, strides) self.assertTrue(imp.suboffsets is None) self.assertFalse(imp.readonly) self.assertEqual(imp.buf, snd._samples_address) imp = Importer(snd, buftools.PyBUF_C_CONTIGUOUS) self.assertEqual(imp.ndim, ndim) self.assertTrue(imp.format is None) self.assertEqual(imp.strides, strides) imp = Importer(snd, buftools.PyBUF_ANY_CONTIGUOUS) self.assertEqual(imp.ndim, ndim) self.assertTrue(imp.format is None) self.assertEqual(imp.strides, strides) if ndim == 1: imp = Importer(snd, buftools.PyBUF_F_CONTIGUOUS) self.assertEqual(imp.ndim, 1) self.assertTrue(imp.format is None) self.assertEqual(imp.strides, strides) else: self.assertRaises(BufferError, Importer, snd, buftools.PyBUF_F_CONTIGUOUS) def test_fadeout(self): """Ensure pygame.mixer.fadeout() stops playback after fading out the sound.""" if mixer.get_init() is None: mixer.init() sound = pygame.mixer.Sound(example_path("data/house_lo.wav")) channel = pygame.mixer.find_channel() channel.play(sound) fadeout_time = 200 # milliseconds channel.fadeout(fadeout_time) pygame.time.wait(fadeout_time + 30) # Ensure the channel is no longer busy self.assertFalse(channel.get_busy()) def test_find_channel(self): # __doc__ (as of 2008-08-02) for pygame.mixer.find_channel: # pygame.mixer.find_channel(force=False): return Channel # find an unused channel mixer.init() filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) num_channels = mixer.get_num_channels() if num_channels > 0: found_channel = mixer.find_channel() self.assertIsNotNone(found_channel) # try playing on all channels channels = [] for channel_id in range(0, num_channels): channel = mixer.Channel(channel_id) channel.play(sound) channels.append(channel) # should fail without being forceful found_channel = mixer.find_channel() self.assertIsNone(found_channel) # try forcing without keyword found_channel = mixer.find_channel(True) self.assertIsNotNone(found_channel) # try forcing with keyword found_channel = mixer.find_channel(force=True) self.assertIsNotNone(found_channel) for channel in channels: channel.stop() found_channel = mixer.find_channel() self.assertIsNotNone(found_channel) @unittest.expectedFailure def test_pause(self): """Ensure pygame.mixer.pause() temporarily stops playback of all sound channels.""" if mixer.get_init() is None: mixer.init() sound = mixer.Sound(example_path("data/house_lo.wav")) channel = mixer.find_channel() channel.play(sound) mixer.pause() # TODO: this currently fails? # Ensure the channel is paused self.assertFalse(channel.get_busy()) mixer.unpause() # Ensure the channel is no longer paused self.assertTrue(channel.get_busy()) def test_set_reserved(self): """Ensure pygame.mixer.set_reserved() reserves the given number of channels.""" # pygame.mixer.set_reserved(count): return count mixer.init() default_num_channels = mixer.get_num_channels() # try reserving all the channels result = mixer.set_reserved(default_num_channels) self.assertEqual(result, default_num_channels) # try reserving all the channels + 1 result = mixer.set_reserved(default_num_channels + 1) # should still be default self.assertEqual(result, default_num_channels) # try unreserving all result = mixer.set_reserved(0) # should still be default self.assertEqual(result, 0) # try reserving half result = mixer.set_reserved(int(default_num_channels / 2)) # should still be default self.assertEqual(result, int(default_num_channels / 2)) def test_stop(self): """Stops playback of all active sound channels.""" if mixer.get_init() is None: mixer.init() sound = pygame.mixer.Sound(example_path("data/house_lo.wav")) channel = pygame.mixer.Channel(0) channel.play(sound) pygame.mixer.stop() for i in range(pygame.mixer.get_num_channels()): self.assertFalse(pygame.mixer.Channel(i).get_busy()) def test_get_sdl_mixer_version(self): """Ensures get_sdl_mixer_version works correctly with no args.""" expected_length = 3 expected_type = tuple expected_item_type = int version = pygame.mixer.get_sdl_mixer_version() self.assertIsInstance(version, expected_type) self.assertEqual(len(version), expected_length) for item in version: self.assertIsInstance(item, expected_item_type) def test_get_sdl_mixer_version__args(self): """Ensures get_sdl_mixer_version works correctly using args.""" expected_length = 3 expected_type = tuple expected_item_type = int for value in (True, False): version = pygame.mixer.get_sdl_mixer_version(value) self.assertIsInstance(version, expected_type) self.assertEqual(len(version), expected_length) for item in version: self.assertIsInstance(item, expected_item_type) def test_get_sdl_mixer_version__kwargs(self): """Ensures get_sdl_mixer_version works correctly using kwargs.""" expected_length = 3 expected_type = tuple expected_item_type = int for value in (True, False): version = pygame.mixer.get_sdl_mixer_version(linked=value) self.assertIsInstance(version, expected_type) self.assertEqual(len(version), expected_length) for item in version: self.assertIsInstance(item, expected_item_type) def test_get_sdl_mixer_version__invalid_args_kwargs(self): """Ensures get_sdl_mixer_version handles invalid args and kwargs.""" invalid_bool = InvalidBool() with self.assertRaises(TypeError): version = pygame.mixer.get_sdl_mixer_version(invalid_bool) with self.assertRaises(TypeError): version = pygame.mixer.get_sdl_mixer_version(linked=invalid_bool) def test_get_sdl_mixer_version__linked_equals_compiled(self): """Ensures get_sdl_mixer_version's linked/compiled versions are equal.""" linked_version = pygame.mixer.get_sdl_mixer_version(linked=True) complied_version = pygame.mixer.get_sdl_mixer_version(linked=False) self.assertTupleEqual(linked_version, complied_version) ############################## CHANNEL CLASS TESTS ############################# class ChannelTypeTest(unittest.TestCase): @classmethod def setUpClass(cls): # Initializing the mixer is slow, so minimize the times it is called. mixer.init() @classmethod def tearDownClass(cls): mixer.quit() def setUp(cls): # This makes sure the mixer is always initialized before each test (in # case a test calls pygame.mixer.quit()). if mixer.get_init() is None: mixer.init() def test_channel(self): """Ensure Channel() creation works.""" channel = mixer.Channel(0) self.assertIsInstance(channel, mixer.ChannelType) self.assertEqual(channel.__class__.__name__, "Channel") def test_channel__without_arg(self): """Ensure exception for Channel() creation with no argument.""" with self.assertRaises(TypeError): mixer.Channel() def test_channel__invalid_id(self): """Ensure exception for Channel() creation with an invalid id.""" with self.assertRaises(IndexError): mixer.Channel(-1) def test_channel__before_init(self): """Ensure exception for Channel() creation with non-init mixer.""" mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): mixer.Channel(0) def test_fadeout(self): """Ensure Channel.fadeout() stops playback after fading out.""" channel = mixer.Channel(0) sound = mixer.Sound(example_path("data/house_lo.wav")) channel.play(sound) fadeout_time = 1000 channel.fadeout(fadeout_time) # Wait for the fadeout to complete. pygame.time.wait(fadeout_time + 100) self.assertFalse(channel.get_busy()) def test_get_busy(self): """Ensure an idle channel's busy state is correct.""" expected_busy = False channel = mixer.Channel(0) busy = channel.get_busy() self.assertEqual(busy, expected_busy) def test_get_busy__active(self): """Ensure an active channel's busy state is correct.""" channel = mixer.Channel(0) sound = mixer.Sound(example_path("data/house_lo.wav")) channel.play(sound) self.assertTrue(channel.get_busy()) def todo_test_get_endevent(self): # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.get_endevent: # Channel.get_endevent(): return type # get the event a channel sends when playback stops # # Returns the event type to be sent every time the Channel finishes # playback of a Sound. If there is no endevent the function returns # pygame.NOEVENT. # self.fail() def test_get_queue(self): """Ensure Channel.get_queue() returns any queued Sound.""" channel = mixer.Channel(0) frequency, format, channels = mixer.get_init() sound_length_in_ms = 200 sound_length_in_ms_2 = 400 bytes_per_ms = int((frequency / 1000) * channels * (abs(format) // 8)) sound1 = mixer.Sound(b"\x00" * int(sound_length_in_ms * bytes_per_ms)) sound2 = mixer.Sound(b"\x00" * (int(sound_length_in_ms_2 * bytes_per_ms))) channel.play(sound1) channel.queue(sound2) # Ensure the second queued sound is returned. self.assertEqual(channel.get_queue().get_length(), sound2.get_length()) # TODO: should sound1.stop() clear it from the queue too? Currently it doesn't. pygame.time.wait(sound_length_in_ms + 100) # TODO: I think here there should be nothing queued. # Because the sound should be off the queue. Currently it doesn't do this. # self.assertIsNone(channel.get_queue()) # the second sound is now playing self.assertEqual(channel.get_sound().get_length(), sound2.get_length()) pygame.time.wait((sound_length_in_ms_2) + 100) # Now there is nothing on the queue. self.assertIsNone(channel.get_queue()) def test_get_sound(self): """Ensure Channel.get_sound() returns the currently playing Sound.""" channel = mixer.Channel(0) sound = mixer.Sound(example_path("data/house_lo.wav")) channel.play(sound) # Ensure the correct Sound object is returned. got_sound = channel.get_sound() self.assertEqual(got_sound, sound) # Stop the sound and ensure None is returned. channel.stop() got_sound = channel.get_sound() self.assertIsNone(got_sound) def test_get_volume(self): """Ensure a channel's volume can be retrieved.""" expected_volume = 1.0 # default channel = mixer.Channel(0) volume = channel.get_volume() self.assertAlmostEqual(volume, expected_volume) def test_pause_unpause(self): """ Test if the Channel can be paused and unpaused. """ if mixer.get_init() is None: mixer.init() sound = pygame.mixer.Sound(example_path("data/house_lo.wav")) channel = sound.play() channel.pause() self.assertTrue( channel.get_busy(), msg="Channel should be paused but it's not." ) channel.unpause() self.assertTrue( channel.get_busy(), msg="Channel should be unpaused but it's not." ) sound.stop() def test_pause_unpause__before_init(self): """ Ensure exception for Channel.pause() with non-init mixer. """ sound = mixer.Sound(example_path("data/house_lo.wav")) channel = sound.play() mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): channel.pause() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): channel.unpause() def todo_test_queue(self): # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.queue: # Channel.queue(Sound): return None # queue a Sound object to follow the current # # When a Sound is queued on a Channel, it will begin playing # immediately after the current Sound is finished. Each channel can # only have a single Sound queued at a time. The queued Sound will # only play if the current playback finished automatically. It is # cleared on any other call to Channel.stop() or Channel.play(). # # If there is no sound actively playing on the Channel then the Sound # will begin playing immediately. # self.fail() def test_stop(self): # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.stop: # Channel.stop(): return None # stop playback on a Channel # # Stop sound playback on a channel. After playback is stopped the # channel becomes available for new Sounds to play on it. # channel = mixer.Channel(0) sound = mixer.Sound(example_path("data/house_lo.wav")) # simple check channel.play(sound) channel.stop() self.assertFalse(channel.get_busy()) # check that queued sounds also stop channel.queue(sound) channel.stop() self.assertFalse(channel.get_busy()) # check that new sounds can be played channel.play(sound) self.assertTrue(channel.get_busy()) class ChannelSetVolumeTest(unittest.TestCase): def setUp(self): mixer.init() self.channel = pygame.mixer.Channel(0) self.sound = pygame.mixer.Sound(example_path("data/boom.wav")) def tearDown(self): mixer.quit() def test_set_volume_with_one_argument(self): self.channel.play(self.sound) self.channel.set_volume(0.5) self.assertEqual(self.channel.get_volume(), 0.5) @unittest.expectedFailure def test_set_volume_with_two_arguments(self): # TODO: why doesn't this work? Seems to ignore stereo setting. # https://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Channel.set_volume self.channel.play(self.sound) self.channel.set_volume(0.3, 0.7) self.assertEqual(self.channel.get_volume(), (0.3, 0.7)) class ChannelEndEventTest(unittest.TestCase): def setUp(self): pygame.display.init() pygame.display.set_mode((40, 40)) if mixer.get_init() is None: mixer.init() def tearDown(self): pygame.display.quit() mixer.quit() def test_get_endevent(self): """Ensure Channel.get_endevent() returns the correct event type.""" channel = mixer.Channel(0) sound = mixer.Sound(example_path("data/house_lo.wav")) channel.play(sound) # Set the end event for the channel. END_EVENT = pygame.USEREVENT + 1 channel.set_endevent(END_EVENT) got_end_event = channel.get_endevent() self.assertEqual(got_end_event, END_EVENT) # Wait for the sound to finish playing. channel.stop() while channel.get_busy(): pygame.time.wait(10) # Check that the end event was sent. events = pygame.event.get(got_end_event) self.assertTrue(len(events) > 0) ############################### SOUND CLASS TESTS ############################## class TestSoundPlay(unittest.TestCase): def setUp(self): mixer.init() self.filename = example_path(os.path.join("data", "house_lo.wav")) self.sound = mixer.Sound(file=self.filename) def tearDown(self): mixer.quit() def test_play_once(self): """Test playing a sound once.""" channel = self.sound.play() self.assertIsInstance(channel, pygame.mixer.Channel) self.assertTrue(channel.get_busy()) def test_play_multiple_times(self): """Test playing a sound multiple times.""" # create a sound 100ms long frequency, format, channels = mixer.get_init() sound_length_in_ms = 100 bytes_per_ms = int((frequency / 1000) * channels * (abs(format) // 8)) sound = mixer.Sound(b"\x00" * int(sound_length_in_ms * bytes_per_ms)) self.assertAlmostEqual( sound.get_length(), sound_length_in_ms / 1000.0, places=2 ) num_loops = 5 channel = sound.play(loops=num_loops) self.assertIsInstance(channel, pygame.mixer.Channel) # the sound should be playing pygame.time.wait((sound_length_in_ms * num_loops) - 100) self.assertTrue(channel.get_busy()) # the sound should not be playing anymore pygame.time.wait(sound_length_in_ms + 200) self.assertFalse(channel.get_busy()) def test_play_indefinitely(self): """Test playing a sound indefinitely.""" frequency, format, channels = mixer.get_init() sound_length_in_ms = 100 bytes_per_ms = int((frequency / 1000) * channels * (abs(format) // 8)) sound = mixer.Sound(b"\x00" * int(sound_length_in_ms * bytes_per_ms)) channel = sound.play(loops=-1) self.assertIsInstance(channel, pygame.mixer.Channel) # we can't wait forever... so we wait 2 loops for _ in range(2): self.assertTrue(channel.get_busy()) pygame.time.wait(sound_length_in_ms) def test_play_with_maxtime(self): """Test playing a sound with maxtime.""" channel = self.sound.play(maxtime=200) self.assertIsInstance(channel, pygame.mixer.Channel) self.assertTrue(channel.get_busy()) pygame.time.wait(200 + 50) self.assertFalse(channel.get_busy()) def test_play_with_fade_ms(self): """Test playing a sound with fade_ms.""" channel = self.sound.play(fade_ms=500) self.assertIsInstance(channel, pygame.mixer.Channel) self.assertTrue(channel.get_busy()) pygame.time.wait(250) self.assertGreater(channel.get_volume(), 0.3) self.assertLess(channel.get_volume(), 0.80) pygame.time.wait(300) self.assertEqual(channel.get_volume(), 1.0) def test_play_with_invalid_loops(self): """Test playing a sound with invalid loops.""" with self.assertRaises(TypeError): self.sound.play(loops="invalid") def test_play_with_invalid_maxtime(self): """Test playing a sound with invalid maxtime.""" with self.assertRaises(TypeError): self.sound.play(maxtime="invalid") def test_play_with_invalid_fade_ms(self): """Test playing a sound with invalid fade_ms.""" with self.assertRaises(TypeError): self.sound.play(fade_ms="invalid") class SoundTypeTest(unittest.TestCase): @classmethod def tearDownClass(cls): mixer.quit() def setUp(cls): # This makes sure the mixer is always initialized before each test (in # case a test calls pygame.mixer.quit()). if mixer.get_init() is None: mixer.init() # See MixerModuleTest's methods test_sound_args(), test_sound_unicode(), # and test_array_keyword() for additional testing of Sound() creation. def test_sound(self): """Ensure Sound() creation with a filename works.""" filename = example_path(os.path.join("data", "house_lo.wav")) sound1 = mixer.Sound(filename) sound2 = mixer.Sound(file=filename) self.assertIsInstance(sound1, mixer.Sound) self.assertIsInstance(sound2, mixer.Sound) def test_sound__from_file_object(self): """Ensure Sound() creation with a file object works.""" filename = example_path(os.path.join("data", "house_lo.wav")) # Using 'with' ensures the file is closed even if test fails. with open(filename, "rb") as file_obj: sound = mixer.Sound(file_obj) self.assertIsInstance(sound, mixer.Sound) def test_sound__from_sound_object(self): """Ensure Sound() creation with a Sound() object works.""" filename = example_path(os.path.join("data", "house_lo.wav")) sound_obj = mixer.Sound(file=filename) sound = mixer.Sound(sound_obj) self.assertIsInstance(sound, mixer.Sound) def test_sound__from_pathlib(self): """Ensure Sound() creation with a pathlib.Path object works.""" path = pathlib.Path(example_path(os.path.join("data", "house_lo.wav"))) sound1 = mixer.Sound(path) sound2 = mixer.Sound(file=path) self.assertIsInstance(sound1, mixer.Sound) self.assertIsInstance(sound2, mixer.Sound) def todo_test_sound__from_buffer(self): """Ensure Sound() creation with a buffer works.""" self.fail() def todo_test_sound__from_array(self): """Ensure Sound() creation with an array works.""" self.fail() def test_sound__without_arg(self): """Ensure exception raised for Sound() creation with no argument.""" with self.assertRaises(TypeError): mixer.Sound() def test_sound__before_init(self): """Ensure exception raised for Sound() creation with non-init mixer.""" mixer.quit() filename = example_path(os.path.join("data", "house_lo.wav")) with self.assertRaisesRegex(pygame.error, "mixer not initialized"): mixer.Sound(file=filename) @unittest.skipIf(IS_PYPY, "pypy skip") def test_samples_address(self): """Test the _samples_address getter.""" try: from ctypes import pythonapi, c_void_p, py_object Bytes_FromString = pythonapi.PyBytes_FromString Bytes_FromString.restype = c_void_p Bytes_FromString.argtypes = [py_object] samples = b"abcdefgh" # keep byte size a multiple of 4 sample_bytes = Bytes_FromString(samples) snd = mixer.Sound(buffer=samples) self.assertNotEqual(snd._samples_address, sample_bytes) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): snd._samples_address def test_get_length(self): """Tests if get_length returns a correct length.""" try: for size in SIZES: pygame.mixer.quit() pygame.mixer.init(size=size) filename = example_path(os.path.join("data", "punch.wav")) sound = mixer.Sound(file=filename) # The sound data is in the mixer output format. So dividing the # length of the raw sound data by the mixer settings gives # the expected length of the sound. sound_bytes = sound.get_raw() mix_freq, mix_bits, mix_channels = pygame.mixer.get_init() mix_bytes = abs(mix_bits) / 8 expected_length = ( float(len(sound_bytes)) / mix_freq / mix_bytes / mix_channels ) self.assertAlmostEqual(expected_length, sound.get_length()) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.get_length() def test_get_num_channels(self): """ Tests if Sound.get_num_channels returns the correct number of channels playing a specific sound. """ try: filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) self.assertEqual(sound.get_num_channels(), 0) sound.play() self.assertEqual(sound.get_num_channels(), 1) sound.play() self.assertEqual(sound.get_num_channels(), 2) sound.stop() self.assertEqual(sound.get_num_channels(), 0) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.get_num_channels() def test_get_volume(self): """Ensure a sound's volume can be retrieved.""" try: expected_volume = 1.0 # default filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) volume = sound.get_volume() self.assertAlmostEqual(volume, expected_volume) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.get_volume() def test_get_volume__while_playing(self): """Ensure a sound's volume can be retrieved while playing.""" try: expected_volume = 1.0 # default filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) sound.play(-1) volume = sound.get_volume() self.assertAlmostEqual(volume, expected_volume) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.get_volume() def test_set_volume(self): """Ensure a sound's volume can be set.""" try: float_delta = 1.0 / 128 # SDL volume range is 0 to 128 filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) current_volume = sound.get_volume() # (volume_set_value : expected_volume) volumes = ( (-1, current_volume), # value < 0 won't change volume (0, 0.0), (0.01, 0.01), (0.1, 0.1), (0.5, 0.5), (0.9, 0.9), (0.99, 0.99), (1, 1.0), (1.1, 1.0), (2.0, 1.0), ) for volume_set_value, expected_volume in volumes: sound.set_volume(volume_set_value) self.assertAlmostEqual( sound.get_volume(), expected_volume, delta=float_delta ) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.set_volume(1) def test_set_volume__while_playing(self): """Ensure a sound's volume can be set while playing.""" try: float_delta = 1.0 / 128 # SDL volume range is 0 to 128 filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) current_volume = sound.get_volume() # (volume_set_value : expected_volume) volumes = ( (-1, current_volume), # value < 0 won't change volume (0, 0.0), (0.01, 0.01), (0.1, 0.1), (0.5, 0.5), (0.9, 0.9), (0.99, 0.99), (1, 1.0), (1.1, 1.0), (2.0, 1.0), ) sound.play(loops=-1) for volume_set_value, expected_volume in volumes: sound.set_volume(volume_set_value) self.assertAlmostEqual( sound.get_volume(), expected_volume, delta=float_delta ) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.set_volume(1) def test_stop(self): """Ensure stop can be called while not playing a sound.""" try: expected_channels = 0 filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) sound.stop() self.assertEqual(sound.get_num_channels(), expected_channels) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.stop() def test_stop__while_playing(self): """Ensure stop stops a playing sound.""" try: expected_channels = 0 filename = example_path(os.path.join("data", "house_lo.wav")) sound = mixer.Sound(file=filename) sound.play(-1) sound.stop() self.assertEqual(sound.get_num_channels(), expected_channels) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): sound.stop() def test_get_raw(self): """Ensure get_raw returns the correct bytestring.""" try: samples = b"abcdefgh" # keep byte size a multiple of 4 snd = mixer.Sound(buffer=samples) raw = snd.get_raw() self.assertIsInstance(raw, bytes) self.assertEqual(raw, samples) finally: pygame.mixer.quit() with self.assertRaisesRegex(pygame.error, "mixer not initialized"): snd.get_raw() def test_correct_subclassing(self): class CorrectSublass(mixer.Sound): def __init__(self, file): super().__init__(file=file) filename = example_path(os.path.join("data", "house_lo.wav")) correct = CorrectSublass(filename) try: correct.get_volume() except Exception: self.fail("This should not raise an exception.") def test_incorrect_subclassing(self): class IncorrectSuclass(mixer.Sound): def __init__(self): pass incorrect = IncorrectSuclass() self.assertRaises(RuntimeError, incorrect.get_volume) class TestSoundFadeout(unittest.TestCase): def setUp(self): if mixer.get_init() is None: pygame.mixer.init() def tearDown(self): pygame.mixer.quit() def test_fadeout_with_valid_time(self): """Tests if fadeout stops sound playback after fading it out over the time argument in milliseconds.""" filename = example_path(os.path.join("data", "punch.wav")) sound = mixer.Sound(file=filename) channel = sound.play() channel.fadeout(1000) pygame.time.wait(2000) self.assertFalse(channel.get_busy()) # TODO: this fails. # def test_fadeout_with_zero_time(self): # """Tests if fadeout stops sound playback immediately when time argument is zero.""" # filename = example_path(os.path.join("data", "punch.wav")) # sound = mixer.Sound(file=filename) # channel = sound.play() # channel.fadeout(0) # self.assertFalse(channel.get_busy()) # TODO: this fails. # def test_fadeout_with_negative_time(self): # """Tests if fadeout stops sound playback immediately when time argument is negative.""" # filename = example_path(os.path.join("data", "punch.wav")) # sound = mixer.Sound(file=filename) # channel = sound.play() # channel.fadeout(-1000) # self.assertFalse(channel.get_busy()) # TODO: What should happen here? # def test_fadeout_with_large_time(self): # """Tests if fadeout stops sound playback after fading it out over the time argument in milliseconds, even if time is larger than the sound length.""" # filename = example_path(os.path.join("data", "punch.wav")) # sound = mixer.Sound(file=filename) # channel = sound.play() # channel.fadeout(...?) # pygame.time.wait(...?) # self.assertFalse(channel.get_busy()) class TestGetBusy(unittest.TestCase): """Test pygame.mixer.get_busy. |tags:slow| """ def setUp(self): pygame.mixer.init() def tearDown(self): pygame.mixer.quit() def test_no_sound_playing(self): """ Test that get_busy returns False when no sound is playing. """ self.assertFalse(pygame.mixer.get_busy()) def test_one_sound_playing(self): """ Test that get_busy returns True when one sound is playing. """ sound = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound.play() time.sleep(0.2) self.assertTrue(pygame.mixer.get_busy()) sound.stop() def test_multiple_sounds_playing(self): """ Test that get_busy returns True when multiple sounds are playing. """ sound1 = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound2 = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound1.play() sound2.play() time.sleep(0.2) self.assertTrue(pygame.mixer.get_busy()) sound1.stop() sound2.stop() def test_all_sounds_stopped(self): """ Test that get_busy returns False when all sounds are stopped. """ sound1 = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound2 = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound1.play() sound2.play() time.sleep(0.2) sound1.stop() sound2.stop() time.sleep(0.2) self.assertFalse(pygame.mixer.get_busy()) def test_all_sounds_stopped_with_fadeout(self): """ Test that get_busy returns False when all sounds are stopped with fadeout. """ sound1 = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound2 = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound1.play() sound2.play() time.sleep(0.2) sound1.fadeout(100) sound2.fadeout(100) time.sleep(0.3) self.assertFalse(pygame.mixer.get_busy()) def test_sound_fading_out(self): """Tests that get_busy() returns True when a sound is fading out""" sound = pygame.mixer.Sound(example_path("data/house_lo.wav")) sound.play(fade_ms=1000) time.sleep(1.1) self.assertTrue(pygame.mixer.get_busy()) sound.stop() ##################################### MAIN ##################################### if __name__ == "__main__": unittest.main()