1# -*- coding: utf8 -*-
2
3import sys
4import os
5import unittest
6import platform
7
8from pygame.tests.test_utils import example_path, AssertRaisesRegexMixin
9
10import pygame
11from pygame import mixer
12from pygame.compat import unicode_, as_bytes, bytes_
13
14try:
15    import pathlib
16except ImportError:
17    pathlib = None
18
19IS_PYPY = "PyPy" == platform.python_implementation()
20
21################################### CONSTANTS ##################################
22
23FREQUENCIES = [11025, 22050, 44100, 48000]
24SIZES = [-16, -8, 8, 16]
25if pygame.get_sdl_version()[0] >= 2:
26    SIZES.append(32)
27
28CHANNELS = [1, 2]
29BUFFERS = [3024]
30
31CONFIGS = [
32    {"frequency": f, "size": s, "channels": c}
33    for f in FREQUENCIES
34    for s in SIZES
35    for c in CHANNELS
36]
37# Using all CONFIGS fails on a Mac; probably older SDL_mixer; we could do:
38# if platform.system() == 'Darwin':
39# But using all CONFIGS is very slow (> 10 sec for example)
40# And probably, we don't need to be so exhaustive, hence:
41
42CONFIG = {"frequency": 22050, "size": -16, "channels": 2}  # base config
43if pygame.get_sdl_version()[0] >= 2:
44    # base config
45    CONFIG = {"frequency": 44100, "size": 32, "channels": 2, "allowedchanges": 0}
46
47
48class InvalidBool(object):
49    """To help test invalid bool values."""
50
51    __nonzero__ = None
52    __bool__ = None
53
54
55############################## MODULE LEVEL TESTS ##############################
56
57
58class MixerModuleTest(unittest.TestCase):
59    def tearDown(self):
60        mixer.quit()
61        mixer.pre_init(0, 0, 0, 0)
62
63    def test_init__keyword_args(self):
64        # note: this test used to loop over all CONFIGS, but it's very slow..
65        mixer.init(**CONFIG)
66        mixer_conf = mixer.get_init()
67
68        self.assertEqual(mixer_conf[0], CONFIG["frequency"])
69        # Not all "sizes" are supported on all systems,  hence "abs".
70        self.assertEqual(abs(mixer_conf[1]), abs(CONFIG["size"]))
71        self.assertGreaterEqual(mixer_conf[2], CONFIG["channels"])
72
73    def test_pre_init__keyword_args(self):
74        # note: this test used to loop over all CONFIGS, but it's very slow..
75        mixer.pre_init(**CONFIG)
76        mixer.init()
77
78        mixer_conf = mixer.get_init()
79
80        self.assertEqual(mixer_conf[0], CONFIG["frequency"])
81        # Not all "sizes" are supported on all systems,  hence "abs".
82        self.assertEqual(abs(mixer_conf[1]), abs(CONFIG["size"]))
83        self.assertGreaterEqual(mixer_conf[2], CONFIG["channels"])
84
85    def test_pre_init__zero_values(self):
86        # Ensure that argument values of 0 are replaced with
87        # default values. No way to check buffer size though.
88        mixer.pre_init(22050, -8, 1)  # Non default values
89        mixer.pre_init(0, 0, 0)  # Should reset to default values
90        mixer.init(allowedchanges=0)
91        self.assertEqual(mixer.get_init()[0], 44100)
92        self.assertEqual(mixer.get_init()[1], -16)
93        self.assertGreaterEqual(mixer.get_init()[2], 2)
94
95    def test_init__zero_values(self):
96        # Ensure that argument values of 0 are replaced with
97        # preset values. No way to check buffer size though.
98        mixer.pre_init(44100, 8, 1, allowedchanges=0)  # None default values
99        mixer.init(0, 0, 0)
100        self.assertEqual(mixer.get_init(), (44100, 8, 1))
101
102    @unittest.skip("SDL_mixer bug")
103    def test_get_init__returns_exact_values_used_for_init(self):
104        # fix in 1.9 - I think it's a SDL_mixer bug.
105
106        # TODO: When this bug is fixed, testing through every combination
107        #       will be too slow so adjust as necessary, at the moment it
108        #       breaks the loop after first failure
109
110        for init_conf in CONFIGS:
111            frequency, size, channels
112            if (frequency, size) == (22050, 16):
113                continue
114            mixer.init(frequency, size, channels)
115
116            mixer_conf = mixer.get_init()
117
118            self.assertEqual(init_conf, mixer_conf)
119            mixer.quit()
120
121    def test_get_init__returns_None_if_mixer_not_initialized(self):
122        self.assertIsNone(mixer.get_init())
123
124    def test_get_num_channels__defaults_eight_after_init(self):
125        mixer.init()
126        self.assertEqual(mixer.get_num_channels(), 8)
127
128    def test_set_num_channels(self):
129        mixer.init()
130
131        default_num_channels = mixer.get_num_channels()
132        for i in range(1, default_num_channels + 1):
133            mixer.set_num_channels(i)
134            self.assertEqual(mixer.get_num_channels(), i)
135
136    def test_quit(self):
137        """ get_num_channels() Should throw pygame.error if uninitialized
138        after mixer.quit() """
139        mixer.init()
140        mixer.quit()
141        self.assertRaises(pygame.error, mixer.get_num_channels)
142
143    # TODO: FIXME: appveyor fails here sometimes.
144    @unittest.skipIf(sys.platform.startswith("win"), "See github issue 892.")
145    def test_sound_args(self):
146        def get_bytes(snd):
147            return snd.get_raw()
148
149        mixer.init()
150
151        sample = as_bytes("\x00\xff") * 24
152        wave_path = example_path(os.path.join("data", "house_lo.wav"))
153        uwave_path = unicode_(wave_path)
154        bwave_path = uwave_path.encode(sys.getfilesystemencoding())
155        snd = mixer.Sound(file=wave_path)
156        self.assertTrue(snd.get_length() > 0.5)
157        snd_bytes = get_bytes(snd)
158        self.assertTrue(len(snd_bytes) > 1000)
159
160        self.assertEqual(get_bytes(mixer.Sound(wave_path)), snd_bytes)
161
162        self.assertEqual(get_bytes(mixer.Sound(file=uwave_path)), snd_bytes)
163        self.assertEqual(get_bytes(mixer.Sound(uwave_path)), snd_bytes)
164        arg_emsg = "Sound takes either 1 positional or 1 keyword argument"
165
166        with self.assertRaises(TypeError) as cm:
167            mixer.Sound()
168        self.assertEqual(str(cm.exception), arg_emsg)
169        with self.assertRaises(TypeError) as cm:
170            mixer.Sound(wave_path, buffer=sample)
171        self.assertEqual(str(cm.exception), arg_emsg)
172        with self.assertRaises(TypeError) as cm:
173            mixer.Sound(sample, file=wave_path)
174        self.assertEqual(str(cm.exception), arg_emsg)
175        with self.assertRaises(TypeError) as cm:
176            mixer.Sound(buffer=sample, file=wave_path)
177        self.assertEqual(str(cm.exception), arg_emsg)
178
179        with self.assertRaises(TypeError) as cm:
180            mixer.Sound(foobar=sample)
181        self.assertEqual(str(cm.exception), "Unrecognized keyword argument 'foobar'")
182
183        snd = mixer.Sound(wave_path, **{})
184        self.assertEqual(get_bytes(snd), snd_bytes)
185        snd = mixer.Sound(*[], **{"file": wave_path})
186
187        with self.assertRaises(TypeError) as cm:
188            mixer.Sound([])
189        self.assertEqual(str(cm.exception), "Unrecognized argument (type list)")
190
191        with self.assertRaises(TypeError) as cm:
192            snd = mixer.Sound(buffer=[])
193        emsg = "Expected object with buffer interface: got a list"
194        self.assertEqual(str(cm.exception), emsg)
195
196        ufake_path = unicode_("12345678")
197        self.assertRaises(IOError, mixer.Sound, ufake_path)
198        self.assertRaises(IOError, mixer.Sound, "12345678")
199
200        with self.assertRaises(TypeError) as cm:
201            mixer.Sound(buffer=unicode_("something"))
202        emsg = "Unicode object not allowed as buffer object"
203        self.assertEqual(str(cm.exception), emsg)
204        self.assertEqual(get_bytes(mixer.Sound(buffer=sample)), sample)
205        if type(sample) != str:
206            somebytes = get_bytes(mixer.Sound(sample))
207            # on python 2 we do not allow using string except as file name.
208            self.assertEqual(somebytes, sample)
209        self.assertEqual(get_bytes(mixer.Sound(file=bwave_path)), snd_bytes)
210        self.assertEqual(get_bytes(mixer.Sound(bwave_path)), snd_bytes)
211
212        snd = mixer.Sound(wave_path)
213        with self.assertRaises(TypeError) as cm:
214            mixer.Sound(wave_path, array=snd)
215        self.assertEqual(str(cm.exception), arg_emsg)
216        with self.assertRaises(TypeError) as cm:
217            mixer.Sound(buffer=sample, array=snd)
218        self.assertEqual(str(cm.exception), arg_emsg)
219        snd2 = mixer.Sound(array=snd)
220        self.assertEqual(snd.get_raw(), snd2.get_raw())
221
222    def test_sound_unicode(self):
223        """test non-ASCII unicode path"""
224        mixer.init()
225        import shutil
226
227        ep = unicode_(example_path("data"))
228        temp_file = os.path.join(ep, u"你好.wav")
229        org_file = os.path.join(ep, u"house_lo.wav")
230        shutil.copy(org_file, temp_file)
231        try:
232            with open(temp_file, "rb") as f:
233                pass
234        except IOError:
235            raise unittest.SkipTest("the path cannot be opened")
236
237        try:
238            sound = mixer.Sound(temp_file)
239            del sound
240        finally:
241            os.remove(temp_file)
242
243    @unittest.skipIf(
244        os.environ.get("SDL_AUDIODRIVER") == "disk",
245        "this test fails without real sound card",
246    )
247    def test_array_keyword(self):
248        try:
249            from numpy import (
250                array,
251                arange,
252                zeros,
253                int8,
254                uint8,
255                int16,
256                uint16,
257                int32,
258                uint32,
259            )
260        except ImportError:
261            self.skipTest("requires numpy")
262
263        freq = 22050
264        format_list = [-8, 8, -16, 16]
265        channels_list = [1, 2]
266
267        a_lists = dict((f, []) for f in format_list)
268        a32u_mono = arange(0, 256, 1, uint32)
269        a16u_mono = a32u_mono.astype(uint16)
270        a8u_mono = a32u_mono.astype(uint8)
271        au_list_mono = [(1, a) for a in [a8u_mono, a16u_mono, a32u_mono]]
272        for format in format_list:
273            if format > 0:
274                a_lists[format].extend(au_list_mono)
275        a32s_mono = arange(-128, 128, 1, int32)
276        a16s_mono = a32s_mono.astype(int16)
277        a8s_mono = a32s_mono.astype(int8)
278        as_list_mono = [(1, a) for a in [a8s_mono, a16s_mono, a32s_mono]]
279        for format in format_list:
280            if format < 0:
281                a_lists[format].extend(as_list_mono)
282        a32u_stereo = zeros([a32u_mono.shape[0], 2], uint32)
283        a32u_stereo[:, 0] = a32u_mono
284        a32u_stereo[:, 1] = 255 - a32u_mono
285        a16u_stereo = a32u_stereo.astype(uint16)
286        a8u_stereo = a32u_stereo.astype(uint8)
287        au_list_stereo = [(2, a) for a in [a8u_stereo, a16u_stereo, a32u_stereo]]
288        for format in format_list:
289            if format > 0:
290                a_lists[format].extend(au_list_stereo)
291        a32s_stereo = zeros([a32s_mono.shape[0], 2], int32)
292        a32s_stereo[:, 0] = a32s_mono
293        a32s_stereo[:, 1] = -1 - a32s_mono
294        a16s_stereo = a32s_stereo.astype(int16)
295        a8s_stereo = a32s_stereo.astype(int8)
296        as_list_stereo = [(2, a) for a in [a8s_stereo, a16s_stereo, a32s_stereo]]
297        for format in format_list:
298            if format < 0:
299                a_lists[format].extend(as_list_stereo)
300
301        for format in format_list:
302            for channels in channels_list:
303                try:
304                    mixer.init(freq, format, channels)
305                except pygame.error:
306                    # Some formats (e.g. 16) may not be supported.
307                    continue
308                try:
309                    __, f, c = mixer.get_init()
310                    if f != format or c != channels:
311                        # Some formats (e.g. -8) may not be supported.
312                        continue
313                    for c, a in a_lists[format]:
314                        self._test_array_argument(format, a, c == channels)
315                finally:
316                    mixer.quit()
317
318    def _test_array_argument(self, format, a, test_pass):
319        from numpy import array, all as all_
320
321        try:
322            snd = mixer.Sound(array=a)
323        except ValueError:
324            if not test_pass:
325                return
326            self.fail("Raised ValueError: Format %i, dtype %s" % (format, a.dtype))
327        if not test_pass:
328            self.fail(
329                "Did not raise ValueError: Format %i, dtype %s" % (format, a.dtype)
330            )
331        a2 = array(snd)
332        a3 = a.astype(a2.dtype)
333        lshift = abs(format) - 8 * a.itemsize
334        if lshift >= 0:
335            # This is asymmetric with respect to downcasting.
336            a3 <<= lshift
337        self.assertTrue(all_(a2 == a3), "Format %i, dtype %s" % (format, a.dtype))
338
339    def _test_array_interface_fail(self, a):
340        self.assertRaises(ValueError, mixer.Sound, array=a)
341
342    def test_array_interface(self):
343        mixer.init(22050, -16, 1, allowedchanges=0)
344        snd = mixer.Sound(buffer=as_bytes("\x00\x7f") * 20)
345        d = snd.__array_interface__
346        self.assertTrue(isinstance(d, dict))
347        if pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN:
348            typestr = "<i2"
349        else:
350            typestr = ">i2"
351        self.assertEqual(d["typestr"], typestr)
352        self.assertEqual(d["shape"], (20,))
353        self.assertEqual(d["strides"], (2,))
354        self.assertEqual(d["data"], (snd._samples_address, False))
355
356    @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented")
357    @unittest.skipIf(IS_PYPY, "pypy2 no likey")
358    def test_newbuf__one_channel(self):
359        mixer.init(22050, -16, 1)
360        self._NEWBUF_export_check()
361
362    @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented")
363    @unittest.skipIf(IS_PYPY, "pypy2 no likey")
364    def test_newbuf__twho_channel(self):
365        mixer.init(22050, -16, 2)
366        self._NEWBUF_export_check()
367
368    def _NEWBUF_export_check(self):
369        freq, fmt, channels = mixer.get_init()
370        ndim = 1 if (channels == 1) else 2
371        itemsize = abs(fmt) // 8
372        formats = {
373            8: "B",
374            -8: "b",
375            16: "=H",
376            -16: "=h",
377            32: "=I",
378            -32: "=i",  # 32 and 64 for future consideration
379            64: "=Q",
380            -64: "=q",
381        }
382        format = formats[fmt]
383        from pygame.tests.test_utils import buftools
384
385        Exporter = buftools.Exporter
386        Importer = buftools.Importer
387        is_lil_endian = pygame.get_sdl_byteorder() == pygame.LIL_ENDIAN
388        fsys, frev = ("<", ">") if is_lil_endian else (">", "<")
389        shape = (10, channels)[:ndim]
390        strides = (channels * itemsize, itemsize)[2 - ndim :]
391        exp = Exporter(shape, format=frev + "i")
392        snd = mixer.Sound(array=exp)
393        buflen = len(exp) * itemsize * channels
394        imp = Importer(snd, buftools.PyBUF_SIMPLE)
395        self.assertEqual(imp.ndim, 0)
396        self.assertTrue(imp.format is None)
397        self.assertEqual(imp.len, buflen)
398        self.assertEqual(imp.itemsize, itemsize)
399        self.assertTrue(imp.shape is None)
400        self.assertTrue(imp.strides is None)
401        self.assertTrue(imp.suboffsets is None)
402        self.assertFalse(imp.readonly)
403        self.assertEqual(imp.buf, snd._samples_address)
404        imp = Importer(snd, buftools.PyBUF_WRITABLE)
405        self.assertEqual(imp.ndim, 0)
406        self.assertTrue(imp.format is None)
407        self.assertEqual(imp.len, buflen)
408        self.assertEqual(imp.itemsize, itemsize)
409        self.assertTrue(imp.shape is None)
410        self.assertTrue(imp.strides is None)
411        self.assertTrue(imp.suboffsets is None)
412        self.assertFalse(imp.readonly)
413        self.assertEqual(imp.buf, snd._samples_address)
414        imp = Importer(snd, buftools.PyBUF_FORMAT)
415        self.assertEqual(imp.ndim, 0)
416        self.assertEqual(imp.format, format)
417        self.assertEqual(imp.len, buflen)
418        self.assertEqual(imp.itemsize, itemsize)
419        self.assertTrue(imp.shape is None)
420        self.assertTrue(imp.strides is None)
421        self.assertTrue(imp.suboffsets is None)
422        self.assertFalse(imp.readonly)
423        self.assertEqual(imp.buf, snd._samples_address)
424        imp = Importer(snd, buftools.PyBUF_ND)
425        self.assertEqual(imp.ndim, ndim)
426        self.assertTrue(imp.format is None)
427        self.assertEqual(imp.len, buflen)
428        self.assertEqual(imp.itemsize, itemsize)
429        self.assertEqual(imp.shape, shape)
430        self.assertTrue(imp.strides is None)
431        self.assertTrue(imp.suboffsets is None)
432        self.assertFalse(imp.readonly)
433        self.assertEqual(imp.buf, snd._samples_address)
434        imp = Importer(snd, buftools.PyBUF_STRIDES)
435        self.assertEqual(imp.ndim, ndim)
436        self.assertTrue(imp.format is None)
437        self.assertEqual(imp.len, buflen)
438        self.assertEqual(imp.itemsize, itemsize)
439        self.assertEqual(imp.shape, shape)
440        self.assertEqual(imp.strides, strides)
441        self.assertTrue(imp.suboffsets is None)
442        self.assertFalse(imp.readonly)
443        self.assertEqual(imp.buf, snd._samples_address)
444        imp = Importer(snd, buftools.PyBUF_FULL_RO)
445        self.assertEqual(imp.ndim, ndim)
446        self.assertEqual(imp.format, format)
447        self.assertEqual(imp.len, buflen)
448        self.assertEqual(imp.itemsize, 2)
449        self.assertEqual(imp.shape, shape)
450        self.assertEqual(imp.strides, strides)
451        self.assertTrue(imp.suboffsets is None)
452        self.assertFalse(imp.readonly)
453        self.assertEqual(imp.buf, snd._samples_address)
454        imp = Importer(snd, buftools.PyBUF_FULL_RO)
455        self.assertEqual(imp.ndim, ndim)
456        self.assertEqual(imp.format, format)
457        self.assertEqual(imp.len, buflen)
458        self.assertEqual(imp.itemsize, itemsize)
459        self.assertEqual(imp.shape, exp.shape)
460        self.assertEqual(imp.strides, strides)
461        self.assertTrue(imp.suboffsets is None)
462        self.assertFalse(imp.readonly)
463        self.assertEqual(imp.buf, snd._samples_address)
464        imp = Importer(snd, buftools.PyBUF_C_CONTIGUOUS)
465        self.assertEqual(imp.ndim, ndim)
466        self.assertTrue(imp.format is None)
467        self.assertEqual(imp.strides, strides)
468        imp = Importer(snd, buftools.PyBUF_ANY_CONTIGUOUS)
469        self.assertEqual(imp.ndim, ndim)
470        self.assertTrue(imp.format is None)
471        self.assertEqual(imp.strides, strides)
472        if ndim == 1:
473            imp = Importer(snd, buftools.PyBUF_F_CONTIGUOUS)
474            self.assertEqual(imp.ndim, 1)
475            self.assertTrue(imp.format is None)
476            self.assertEqual(imp.strides, strides)
477        else:
478            self.assertRaises(BufferError, Importer, snd, buftools.PyBUF_F_CONTIGUOUS)
479
480    def todo_test_fadeout(self):
481
482        # __doc__ (as of 2008-08-02) for pygame.mixer.fadeout:
483
484        # pygame.mixer.fadeout(time): return None
485        # fade out the volume on all sounds before stopping
486        #
487        # This will fade out the volume on all active channels over the time
488        # argument in milliseconds. After the sound is muted the playback will
489        # stop.
490        #
491
492        self.fail()
493
494    def test_find_channel(self):
495        # __doc__ (as of 2008-08-02) for pygame.mixer.find_channel:
496
497        # pygame.mixer.find_channel(force=False): return Channel
498        # find an unused channel
499        mixer.init()
500
501        filename = example_path(os.path.join("data", "house_lo.wav"))
502        sound = mixer.Sound(file=filename)
503
504        num_channels = mixer.get_num_channels()
505
506        if num_channels > 0:
507            found_channel = mixer.find_channel()
508            self.assertIsNotNone(found_channel)
509
510            # try playing on all channels
511            channels = []
512            for channel_id in range(0, num_channels):
513                channel = mixer.Channel(channel_id)
514                channel.play(sound)
515                channels.append(channel)
516
517            # should fail without being forceful
518            found_channel = mixer.find_channel()
519            self.assertIsNone(found_channel)
520
521            # try forcing without keyword
522            found_channel = mixer.find_channel(True)
523            self.assertIsNotNone(found_channel)
524
525            # try forcing with keyword
526            found_channel = mixer.find_channel(force=True)
527            self.assertIsNotNone(found_channel)
528
529            for channel in channels:
530                channel.stop()
531            found_channel = mixer.find_channel()
532            self.assertIsNotNone(found_channel)
533
534
535
536
537    def todo_test_get_busy(self):
538
539        # __doc__ (as of 2008-08-02) for pygame.mixer.get_busy:
540
541        # pygame.mixer.get_busy(): return bool
542        # test if any sound is being mixed
543        #
544        # Returns True if the mixer is busy mixing any channels. If the mixer
545        # is idle then this return False.
546        #
547
548        self.fail()
549
550    def todo_test_pause(self):
551
552        # __doc__ (as of 2008-08-02) for pygame.mixer.pause:
553
554        # pygame.mixer.pause(): return None
555        # temporarily stop playback of all sound channels
556        #
557        # This will temporarily stop all playback on the active mixer
558        # channels. The playback can later be resumed with
559        # pygame.mixer.unpause()
560        #
561
562        self.fail()
563
564    def test_set_reserved(self):
565
566        # __doc__ (as of 2008-08-02) for pygame.mixer.set_reserved:
567
568        # pygame.mixer.set_reserved(count): return count
569        mixer.init()
570        default_num_channels = mixer.get_num_channels()
571
572        # try reserving all the channels
573        result = mixer.set_reserved(default_num_channels)
574        self.assertEqual(result, default_num_channels)
575
576        # try reserving all the channels + 1
577        result = mixer.set_reserved(default_num_channels + 1)
578        # should still be default
579        self.assertEqual(result, default_num_channels)
580
581        # try unreserving all
582        result = mixer.set_reserved(0)
583        # should still be default
584        self.assertEqual(result, 0)
585
586        # try reserving half
587        result = mixer.set_reserved(int(default_num_channels/2))
588        # should still be default
589        self.assertEqual(result, int(default_num_channels/2))
590
591    def todo_test_stop(self):
592
593        # __doc__ (as of 2008-08-02) for pygame.mixer.stop:
594
595        # pygame.mixer.stop(): return None
596        # stop playback of all sound channels
597        #
598        # This will stop all playback of all active mixer channels.
599
600        self.fail()
601
602    def todo_test_unpause(self):
603
604        # __doc__ (as of 2008-08-02) for pygame.mixer.unpause:
605
606        # pygame.mixer.unpause(): return None
607        # resume paused playback of sound channels
608        #
609        # This will resume all active sound channels after they have been paused.
610
611        self.fail()
612
613    def test_get_sdl_mixer_version(self):
614        """Ensures get_sdl_mixer_version works correctly with no args."""
615        expected_length = 3
616        expected_type = tuple
617        expected_item_type = int
618
619        version = pygame.mixer.get_sdl_mixer_version()
620
621        self.assertIsInstance(version, expected_type)
622        self.assertEqual(len(version), expected_length)
623
624        for item in version:
625            self.assertIsInstance(item, expected_item_type)
626
627    def test_get_sdl_mixer_version__args(self):
628        """Ensures get_sdl_mixer_version works correctly using args."""
629        expected_length = 3
630        expected_type = tuple
631        expected_item_type = int
632
633        for value in (True, False):
634            version = pygame.mixer.get_sdl_mixer_version(value)
635
636            self.assertIsInstance(version, expected_type)
637            self.assertEqual(len(version), expected_length)
638
639            for item in version:
640                self.assertIsInstance(item, expected_item_type)
641
642    def test_get_sdl_mixer_version__kwargs(self):
643        """Ensures get_sdl_mixer_version works correctly using kwargs."""
644        expected_length = 3
645        expected_type = tuple
646        expected_item_type = int
647
648        for value in (True, False):
649            version = pygame.mixer.get_sdl_mixer_version(linked=value)
650
651            self.assertIsInstance(version, expected_type)
652            self.assertEqual(len(version), expected_length)
653
654            for item in version:
655                self.assertIsInstance(item, expected_item_type)
656
657    def test_get_sdl_mixer_version__invalid_args_kwargs(self):
658        """Ensures get_sdl_mixer_version handles invalid args and kwargs."""
659        invalid_bool = InvalidBool()
660
661        with self.assertRaises(TypeError):
662            version = pygame.mixer.get_sdl_mixer_version(invalid_bool)
663
664        with self.assertRaises(TypeError):
665            version = pygame.mixer.get_sdl_mixer_version(linked=invalid_bool)
666
667    def test_get_sdl_mixer_version__linked_equals_compiled(self):
668        """Ensures get_sdl_mixer_version's linked/compiled versions are equal.
669        """
670        linked_version = pygame.mixer.get_sdl_mixer_version(linked=True)
671        complied_version = pygame.mixer.get_sdl_mixer_version(linked=False)
672
673        self.assertTupleEqual(linked_version, complied_version)
674
675
676############################## CHANNEL CLASS TESTS #############################
677
678
679class ChannelTypeTest(AssertRaisesRegexMixin, unittest.TestCase):
680    @classmethod
681    def setUpClass(cls):
682        # Initializing the mixer is slow, so minimize the times it is called.
683        mixer.init()
684
685    @classmethod
686    def tearDownClass(cls):
687        mixer.quit()
688
689    def setUp(cls):
690        # This makes sure the mixer is always initialized before each test (in
691        # case a test calls pygame.mixer.quit()).
692        if mixer.get_init() is None:
693            mixer.init()
694
695    def test_channel(self):
696        """Ensure Channel() creation works."""
697        channel = mixer.Channel(0)
698
699        self.assertIsInstance(channel, mixer.ChannelType)
700        self.assertEqual(channel.__class__.__name__, "Channel")
701
702    def test_channel__without_arg(self):
703        """Ensure exception for Channel() creation with no argument."""
704        with self.assertRaises(TypeError):
705            mixer.Channel()
706
707    def test_channel__invalid_id(self):
708        """Ensure exception for Channel() creation with an invalid id."""
709        with self.assertRaises(IndexError):
710            mixer.Channel(-1)
711
712    def test_channel__before_init(self):
713        """Ensure exception for Channel() creation with non-init mixer."""
714        mixer.quit()
715
716        with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
717            mixer.Channel(0)
718
719    def todo_test_fadeout(self):
720
721        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.fadeout:
722
723        # Channel.fadeout(time): return None
724        # stop playback after fading channel out
725        #
726        # Stop playback of a channel after fading out the sound over the given
727        # time argument in milliseconds.
728        #
729
730        self.fail()
731
732    def test_get_busy(self):
733        """Ensure an idle channel's busy state is correct."""
734        expected_busy = False
735        channel = mixer.Channel(0)
736
737        busy = channel.get_busy()
738
739        self.assertEqual(busy, expected_busy)
740
741    def todo_test_get_busy__active(self):
742        """Ensure an active channel's busy state is correct."""
743        self.fail()
744
745    def todo_test_get_endevent(self):
746
747        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.get_endevent:
748
749        # Channel.get_endevent(): return type
750        # get the event a channel sends when playback stops
751        #
752        # Returns the event type to be sent every time the Channel finishes
753        # playback of a Sound. If there is no endevent the function returns
754        # pygame.NOEVENT.
755        #
756
757        self.fail()
758
759    def todo_test_get_queue(self):
760
761        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.get_queue:
762
763        # Channel.get_queue(): return Sound
764        # return any Sound that is queued
765        #
766        # If a Sound is already queued on this channel it will be returned.
767        # Once the queued sound begins playback it will no longer be on the
768        # queue.
769        #
770
771        self.fail()
772
773    def todo_test_get_sound(self):
774
775        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.get_sound:
776
777        # Channel.get_sound(): return Sound
778        # get the currently playing Sound
779        #
780        # Return the actual Sound object currently playing on this channel. If
781        # the channel is idle None is returned.
782        #
783
784        self.fail()
785
786    def test_get_volume(self):
787        """Ensure a channel's volume can be retrieved."""
788        expected_volume = 1.0  # default
789        channel = mixer.Channel(0)
790
791        volume = channel.get_volume()
792
793        self.assertAlmostEqual(volume, expected_volume)
794
795    def todo_test_get_volume__while_playing(self):
796        """Ensure a channel's volume can be retrieved while playing."""
797        self.fail()
798
799    def todo_test_pause(self):
800
801        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.pause:
802
803        # Channel.pause(): return None
804        # temporarily stop playback of a channel
805        #
806        # Temporarily stop the playback of sound on a channel. It can be
807        # resumed at a later time with Channel.unpause()
808        #
809
810        self.fail()
811
812    def todo_test_play(self):
813
814        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.play:
815
816        # Channel.play(Sound, loops=0, maxtime=0, fade_ms=0): return None
817        # play a Sound on a specific Channel
818        #
819        # This will begin playback of a Sound on a specific Channel. If the
820        # Channel is currently playing any other Sound it will be stopped.
821        #
822        # The loops argument has the same meaning as in Sound.play(): it is
823        # the number of times to repeat the sound after the first time. If it
824        # is 3, the sound will be played 4 times (the first time, then three
825        # more). If loops is -1 then the playback will repeat indefinitely.
826        #
827        # As in Sound.play(), the maxtime argument can be used to stop
828        # playback of the Sound after a given number of milliseconds.
829        #
830        # As in Sound.play(), the fade_ms argument can be used fade in the sound.
831
832        self.fail()
833
834    def todo_test_queue(self):
835
836        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.queue:
837
838        # Channel.queue(Sound): return None
839        # queue a Sound object to follow the current
840        #
841        # When a Sound is queued on a Channel, it will begin playing
842        # immediately after the current Sound is finished. Each channel can
843        # only have a single Sound queued at a time. The queued Sound will
844        # only play if the current playback finished automatically. It is
845        # cleared on any other call to Channel.stop() or Channel.play().
846        #
847        # If there is no sound actively playing on the Channel then the Sound
848        # will begin playing immediately.
849        #
850
851        self.fail()
852
853    def todo_test_set_endevent(self):
854
855        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.set_endevent:
856
857        # Channel.set_endevent(): return None
858        # Channel.set_endevent(type): return None
859        # have the channel send an event when playback stops
860        #
861        # When an endevent is set for a channel, it will send an event to the
862        # pygame queue every time a sound finishes playing on that channel
863        # (not just the first time). Use pygame.event.get() to retrieve the
864        # endevent once it's sent.
865        #
866        # Note that if you called Sound.play(n) or Channel.play(sound,n), the
867        # end event is sent only once: after the sound has been played "n+1"
868        # times (see the documentation of Sound.play).
869        #
870        # If Channel.stop() or Channel.play() is called while the sound was
871        # still playing, the event will be posted immediately.
872        #
873        # The type argument will be the event id sent to the queue. This can
874        # be any valid event type, but a good choice would be a value between
875        # pygame.locals.USEREVENT and pygame.locals.NUMEVENTS. If no type
876        # argument is given then the Channel will stop sending endevents.
877        #
878
879        self.fail()
880
881    def todo_test_set_volume(self):
882
883        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.set_volume:
884
885        # Channel.set_volume(value): return None
886        # Channel.set_volume(left, right): return None
887        # set the volume of a playing channel
888        #
889        # Set the volume (loudness) of a playing sound. When a channel starts
890        # to play its volume value is reset. This only affects the current
891        # sound. The value argument is between 0.0 and 1.0.
892        #
893        # If one argument is passed, it will be the volume of both speakers.
894        # If two arguments are passed and the mixer is in stereo mode, the
895        # first argument will be the volume of the left speaker and the second
896        # will be the volume of the right speaker. (If the second argument is
897        # None, the first argument will be the volume of both speakers.)
898        #
899        # If the channel is playing a Sound on which set_volume() has also
900        # been called, both calls are taken into account. For example:
901        #
902        #     sound = pygame.mixer.Sound("s.wav")
903        #     channel = s.play()      # Sound plays at full volume by default
904        #     sound.set_volume(0.9)   # Now plays at 90% of full volume.
905        #     sound.set_volume(0.6)   # Now plays at 60% (previous value replaced).
906        #     channel.set_volume(0.5) # Now plays at 30% (0.6 * 0.5).
907
908        self.fail()
909
910    def todo_test_stop(self):
911
912        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.stop:
913
914        # Channel.stop(): return None
915        # stop playback on a Channel
916        #
917        # Stop sound playback on a channel. After playback is stopped the
918        # channel becomes available for new Sounds to play on it.
919        #
920
921        self.fail()
922
923    def todo_test_unpause(self):
924
925        # __doc__ (as of 2008-08-02) for pygame.mixer.Channel.unpause:
926
927        # Channel.unpause(): return None
928        # resume pause playback of a channel
929        #
930        # Resume the playback on a paused channel.
931
932        self.fail()
933
934
935############################### SOUND CLASS TESTS ##############################
936
937
938class SoundTypeTest(AssertRaisesRegexMixin, unittest.TestCase):
939    @classmethod
940    def tearDownClass(cls):
941        mixer.quit()
942
943    def setUp(cls):
944        # This makes sure the mixer is always initialized before each test (in
945        # case a test calls pygame.mixer.quit()).
946        if mixer.get_init() is None:
947            mixer.init()
948
949    # See MixerModuleTest's methods test_sound_args(), test_sound_unicode(),
950    # and test_array_keyword() for additional testing of Sound() creation.
951    def test_sound(self):
952        """Ensure Sound() creation with a filename works."""
953        filename = example_path(os.path.join("data", "house_lo.wav"))
954        sound1 = mixer.Sound(filename)
955        sound2 = mixer.Sound(file=filename)
956
957        self.assertIsInstance(sound1, mixer.Sound)
958        self.assertIsInstance(sound2, mixer.Sound)
959
960    def test_sound__from_file_object(self):
961        """Ensure Sound() creation with a file object works."""
962        filename = example_path(os.path.join("data", "house_lo.wav"))
963
964        # Using 'with' ensures the file is closed even if test fails.
965        with open(filename, "rb") as file_obj:
966            sound = mixer.Sound(file_obj)
967
968            self.assertIsInstance(sound, mixer.Sound)
969
970    def test_sound__from_sound_object(self):
971        """Ensure Sound() creation with a Sound() object works."""
972        filename = example_path(os.path.join("data", "house_lo.wav"))
973        sound_obj = mixer.Sound(file=filename)
974
975        sound = mixer.Sound(sound_obj)
976
977        self.assertIsInstance(sound, mixer.Sound)
978
979    @unittest.skipIf(pathlib is None, "no pathlib")
980    def test_sound__from_pathlib(self):
981        """Ensure Sound() creation with a pathlib.Path object works."""
982        path = pathlib.Path(example_path(os.path.join("data", "house_lo.wav")))
983        sound1 = mixer.Sound(path)
984        sound2 = mixer.Sound(file=path)
985        self.assertIsInstance(sound1, mixer.Sound)
986        self.assertIsInstance(sound2, mixer.Sound)
987
988    def todo_test_sound__from_buffer(self):
989        """Ensure Sound() creation with a buffer works."""
990        self.fail()
991
992    def todo_test_sound__from_array(self):
993        """Ensure Sound() creation with an array works."""
994        self.fail()
995
996    def test_sound__without_arg(self):
997        """Ensure exception raised for Sound() creation with no argument."""
998        with self.assertRaises(TypeError):
999            mixer.Sound()
1000
1001    def test_sound__before_init(self):
1002        """Ensure exception raised for Sound() creation with non-init mixer."""
1003        mixer.quit()
1004        filename = example_path(os.path.join("data", "house_lo.wav"))
1005
1006        with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1007            mixer.Sound(file=filename)
1008
1009    @unittest.skipIf(IS_PYPY, "pypy skip")
1010    def test_samples_address(self):
1011        """Test the _samples_address getter."""
1012        try:
1013            from ctypes import pythonapi, c_void_p, py_object
1014
1015            try:
1016                Bytes_FromString = pythonapi.PyBytes_FromString  # python 3
1017            except:
1018                Bytes_FromString = pythonapi.PyString_FromString  # python 2
1019
1020            Bytes_FromString.restype = c_void_p
1021            Bytes_FromString.argtypes = [py_object]
1022            samples = as_bytes("abcdefgh")  # keep byte size a multiple of 4
1023            sample_bytes = Bytes_FromString(samples)
1024
1025            snd = mixer.Sound(buffer=samples)
1026
1027            self.assertNotEqual(snd._samples_address, sample_bytes)
1028        finally:
1029            pygame.mixer.quit()
1030            with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1031                snd._samples_address
1032
1033    def todo_test_fadeout(self):
1034
1035        # __doc__ (as of 2008-08-02) for pygame.mixer.Sound.fadeout:
1036
1037        # Sound.fadeout(time): return None
1038        # stop sound playback after fading out
1039        #
1040        # This will stop playback of the sound after fading it out over the
1041        # time argument in milliseconds. The Sound will fade and stop on all
1042        # actively playing channels.
1043        #
1044
1045        self.fail()
1046
1047    def test_get_length(self):
1048        """Tests if get_length returns a correct length."""
1049        try:
1050            for size in SIZES:
1051                pygame.mixer.quit()
1052                pygame.mixer.init(size=size)
1053                filename = example_path(os.path.join("data", "punch.wav"))
1054                sound = mixer.Sound(file=filename)
1055                # The sound data is in the mixer output format. So dividing the
1056                # length of the raw sound data by the mixer settings gives
1057                # the expected length of the sound.
1058                sound_bytes = sound.get_raw()
1059                mix_freq, mix_bits, mix_channels = pygame.mixer.get_init()
1060                mix_bytes = abs(mix_bits) / 8
1061                expected_length = float(len(sound_bytes)) / mix_freq / mix_bytes / mix_channels
1062                self.assertAlmostEqual(expected_length, sound.get_length())
1063        finally:
1064            pygame.mixer.quit()
1065            with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1066                sound.get_length()
1067
1068    def test_get_num_channels(self):
1069        """
1070        Tests if Sound.get_num_channels returns the correct number
1071        of channels playing a specific sound.
1072        """
1073        try:
1074            filename = example_path(os.path.join("data", "house_lo.wav"))
1075            sound = mixer.Sound(file=filename)
1076
1077            self.assertEqual(sound.get_num_channels(), 0)
1078            sound.play()
1079            self.assertEqual(sound.get_num_channels(), 1)
1080            sound.play()
1081            self.assertEqual(sound.get_num_channels(), 2)
1082            sound.stop()
1083            self.assertEqual(sound.get_num_channels(), 0)
1084        finally:
1085            pygame.mixer.quit()
1086            with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1087                sound.get_num_channels()
1088
1089    def test_get_volume(self):
1090        """Ensure a sound's volume can be retrieved."""
1091        try:
1092            expected_volume = 1.0  # default
1093            filename = example_path(os.path.join("data", "house_lo.wav"))
1094            sound = mixer.Sound(file=filename)
1095
1096            volume = sound.get_volume()
1097
1098            self.assertAlmostEqual(volume, expected_volume)
1099        finally:
1100            pygame.mixer.quit()
1101            with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1102                sound.get_volume()
1103
1104    def todo_test_get_volume__while_playing(self):
1105        """Ensure a sound's volume can be retrieved while playing."""
1106        self.fail()
1107
1108    def todo_test_play(self):
1109
1110        # __doc__ (as of 2008-08-02) for pygame.mixer.Sound.play:
1111
1112        # Sound.play(loops=0, maxtime=0, fade_ms=0): return Channel
1113        # begin sound playback
1114        #
1115        # Begin playback of the Sound (i.e., on the computer's speakers) on an
1116        # available Channel. This will forcibly select a Channel, so playback
1117        # may cut off a currently playing sound if necessary.
1118        #
1119        # The loops argument controls how many times the sample will be
1120        # repeated after being played the first time. A value of 5 means that
1121        # the sound will be played once, then repeated five times, and so is
1122        # played a total of six times. The default value (zero) means the
1123        # Sound is not repeated, and so is only played once. If loops is set
1124        # to -1 the Sound will loop indefinitely (though you can still call
1125        # stop() to stop it).
1126        #
1127        # The maxtime argument can be used to stop playback after a given
1128        # number of milliseconds.
1129        #
1130        # The fade_ms argument will make the sound start playing at 0 volume
1131        # and fade up to full volume over the time given. The sample may end
1132        # before the fade-in is complete.
1133        #
1134        # This returns the Channel object for the channel that was selected.
1135
1136        self.fail()
1137
1138    def test_set_volume(self):
1139        """Ensure a sound's volume can be set."""
1140        try:
1141            float_delta = 1.0 / 128  # SDL volume range is 0 to 128
1142            filename = example_path(os.path.join("data", "house_lo.wav"))
1143            sound = mixer.Sound(file=filename)
1144            current_volume = sound.get_volume()
1145
1146            # (volume_set_value : expected_volume)
1147            volumes = (
1148                (-1, current_volume),  # value < 0 won't change volume
1149                (0, 0.0),
1150                (0.01, 0.01),
1151                (0.1, 0.1),
1152                (0.5, 0.5),
1153                (0.9, 0.9),
1154                (0.99, 0.99),
1155                (1, 1.0),
1156                (1.1, 1.0),
1157                (2.0, 1.0),
1158            )
1159
1160            for volume_set_value, expected_volume in volumes:
1161                sound.set_volume(volume_set_value)
1162
1163                self.assertAlmostEqual(
1164                    sound.get_volume(), expected_volume, delta=float_delta
1165                )
1166        finally:
1167            pygame.mixer.quit()
1168            with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1169                sound.set_volume(1)
1170
1171    def todo_test_set_volume__while_playing(self):
1172        """Ensure a sound's volume can be set while playing."""
1173        self.fail()
1174
1175    def test_stop(self):
1176        """Ensure stop can be called while not playing a sound."""
1177        try:
1178            expected_channels = 0
1179            filename = example_path(os.path.join("data", "house_lo.wav"))
1180            sound = mixer.Sound(file=filename)
1181
1182            sound.stop()
1183
1184            self.assertEqual(sound.get_num_channels(), expected_channels)
1185        finally:
1186            pygame.mixer.quit()
1187            with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1188                sound.stop()
1189
1190    def todo_test_stop__while_playing(self):
1191        """Ensure stop stops a playing sound."""
1192        self.fail()
1193
1194    def test_get_raw(self):
1195        """Ensure get_raw returns the correct bytestring."""
1196        try:
1197            samples = as_bytes("abcdefgh")  # keep byte size a multiple of 4
1198            snd = mixer.Sound(buffer=samples)
1199
1200            raw = snd.get_raw()
1201
1202            self.assertIsInstance(raw, bytes_)
1203            self.assertEqual(raw, samples)
1204        finally:
1205            pygame.mixer.quit()
1206            with self.assertRaisesRegex(pygame.error, "mixer not initialized"):
1207                snd.get_raw()
1208
1209
1210##################################### MAIN #####################################
1211
1212if __name__ == "__main__":
1213    unittest.main()
1214