Skip to content

Commit 922b63a

Browse files
Add Sound.copy and Sound.__copy__
Co-authored-by: Borishkof <[email protected]>
1 parent 94370b6 commit 922b63a

File tree

5 files changed

+95
-0
lines changed

5 files changed

+95
-0
lines changed

buildconfig/stubs/pygame/mixer.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class Sound:
7272
def get_num_channels(self) -> int: ...
7373
def get_length(self) -> float: ...
7474
def get_raw(self) -> bytes: ...
75+
def copy(self) -> Sound: ...
76+
def __copy__(self) -> Sound: ...
7577

7678
class Channel:
7779
def __init__(self, id: int) -> None: ...

docs/reST/ref/mixer.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,23 @@ The following file formats are supported
500500

501501
.. ## Sound.get_raw ##
502502
503+
.. method:: copy
504+
505+
| :sl:`return a new Sound object that is a deep copy of this Sound`
506+
| :sg:`copy() -> Sound`
507+
| :sg:`copy.copy(original_sound) -> Sound`
508+
509+
Return a new Sound object that is a deep copy of this Sound. The new Sound
510+
will be just as if you loaded it from the same file on disk as you did the
511+
original Sound. If the copy fails, a ``TypeError`` or :meth:`pygame.error`
512+
exception will be raised.
513+
514+
Also note that this functions as ``Sound.__copy__``.
515+
516+
.. versionadded:: 2.5.6
517+
518+
.. ## Sound.copy ##
519+
503520
.. ## pygame.mixer.Sound ##
504521
505522
.. class:: Channel

src_c/doc/mixer_doc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#define DOC_MIXER_SOUND_GETNUMCHANNELS "get_num_channels() -> count\ncount how many times this Sound is playing"
2727
#define DOC_MIXER_SOUND_GETLENGTH "get_length() -> seconds\nget the length of the Sound"
2828
#define DOC_MIXER_SOUND_GETRAW "get_raw() -> bytes\nreturn a bytestring copy of the Sound samples."
29+
#define DOC_MIXER_SOUND_COPY "copy() -> Sound\ncopy.copy(original_sound) -> Sound\nreturn a new Sound object that is a deep copy of this Sound"
2930
#define DOC_MIXER_CHANNEL "Channel(id) -> Channel\nCreate a Channel object for controlling playback"
3031
#define DOC_MIXER_CHANNEL_ID "id -> int\nget the channel id for the Channel object"
3132
#define DOC_MIXER_CHANNEL_PLAY "play(Sound, loops=0, maxtime=0, fade_ms=0) -> None\nplay a Sound on a specific Channel"

src_c/mixer.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pgChannel_New(int);
8989
#define pgChannel_Check(x) \
9090
(PyObject_IsInstance(x, (PyObject *)&pgChannel_Type))
9191

92+
static PyObject *
93+
snd_get_arraystruct(PyObject *self, void *closure);
9294
static int
9395
snd_getbuffer(PyObject *, Py_buffer *, int);
9496
static void
@@ -801,6 +803,37 @@ snd_get_raw(PyObject *self, PyObject *_null)
801803
(Py_ssize_t)chunk->alen);
802804
}
803805

806+
static PyObject *
807+
snd_copy(PyObject *self, PyObject *_null)
808+
{
809+
MIXER_INIT_CHECK();
810+
811+
pgSoundObject *newSound =
812+
(pgSoundObject *)pgSound_Type.tp_new(&pgSound_Type, NULL, NULL);
813+
814+
PyObject *kwargs = PyDict_New();
815+
PyObject *key = PyUnicode_FromString("buffer");
816+
PyObject *bytes = snd_get_raw(self, NULL);
817+
if (PyDict_SetItem(kwargs, key, bytes) < 0) {
818+
// exception set already
819+
Py_DECREF(key);
820+
Py_DECREF(bytes);
821+
Py_DECREF(kwargs);
822+
return NULL;
823+
}
824+
Py_DECREF(key);
825+
Py_DECREF(bytes);
826+
827+
if (sound_init((PyObject *)newSound, NULL, kwargs) != 0) {
828+
Py_DECREF(kwargs);
829+
Py_DECREF(newSound);
830+
return RAISE(pgExc_SDLError, "Failed to initialize copied sound");
831+
}
832+
833+
Py_DECREF(kwargs);
834+
return (PyObject *)newSound;
835+
}
836+
804837
static PyObject *
805838
snd_get_arraystruct(PyObject *self, void *closure)
806839
{
@@ -858,6 +891,8 @@ PyMethodDef sound_methods[] = {
858891
{"get_volume", snd_get_volume, METH_NOARGS, DOC_MIXER_SOUND_GETVOLUME},
859892
{"get_length", snd_get_length, METH_NOARGS, DOC_MIXER_SOUND_GETLENGTH},
860893
{"get_raw", snd_get_raw, METH_NOARGS, DOC_MIXER_SOUND_GETRAW},
894+
{"copy", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
895+
{"__copy__", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
861896
{NULL, NULL, 0, NULL}};
862897

863898
static PyGetSetDef sound_getset[] = {

test/mixer_test.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,46 @@ def __init__(self):
13301330

13311331
self.assertRaises(RuntimeError, incorrect.get_volume)
13321332

1333+
def test_snd_copy(self):
1334+
mixer.init()
1335+
1336+
filenames = [
1337+
"house_lo.ogg",
1338+
"house_lo.wav",
1339+
"house_lo.flac",
1340+
"house_lo.opus",
1341+
"surfonasinewave.xm",
1342+
]
1343+
if pygame.mixer.get_sdl_mixer_version() >= (2, 6, 0):
1344+
filenames.append("house_lo.mp3")
1345+
1346+
for f in filenames:
1347+
filename = example_path(os.path.join("data", f))
1348+
try:
1349+
sound = mixer.Sound(file=filename)
1350+
except pygame.error as e:
1351+
continue
1352+
sound_copy = sound.copy()
1353+
self.assertEqual(sound.get_length(), sound_copy.get_length())
1354+
self.assertEqual(sound.get_num_channels(), sound_copy.get_num_channels())
1355+
self.assertEqual(sound.get_volume(), sound_copy.get_volume())
1356+
self.assertEqual(sound.get_raw(), sound_copy.get_raw())
1357+
1358+
sound.set_volume(0.5)
1359+
self.assertNotEqual(sound.get_volume(), sound_copy.get_volume())
1360+
1361+
del sound
1362+
1363+
# Test on the copy for playable sounds
1364+
channel = sound_copy.play()
1365+
if channel is None:
1366+
continue
1367+
self.assertTrue(channel.get_busy())
1368+
sound_copy.stop()
1369+
self.assertFalse(channel.get_busy())
1370+
sound_copy.play()
1371+
self.assertEqual(sound_copy.get_num_channels(), 1)
1372+
13331373

13341374
##################################### MAIN #####################################
13351375

0 commit comments

Comments
 (0)