Skip to content

Conversation

oddbookworm
Copy link
Member

@oddbookworm oddbookworm commented Aug 10, 2025

I added https://github.com/Borishkof as a co-author because I took the test and tweaked the docs from their old pull request

Summary by CodeRabbit

  • New Features

    • Added Sound.copy() and Sound.copy to produce independent deep copies that preserve volume and return instances of the same subclass; may raise TypeError or pygame.error on failure.
  • Documentation

    • Updated docs to describe copy() behavior, error cases, and note that bytes()/bytearray() use the Sound buffer interface (documentation added in two places).
  • Tests

    • Added tests confirming copied sounds match length/channels/raw data, remain independent, and play correctly (including subclass instances).

@oddbookworm oddbookworm requested a review from a team as a code owner August 10, 2025 02:41
@oddbookworm oddbookworm added New API This pull request may need extra debate as it adds a new class or function to pygame mixer pygame.mixer C API labels Aug 10, 2025
@oddbookworm oddbookworm linked an issue Aug 10, 2025 that may be closed by this pull request
@oddbookworm
Copy link
Member Author

image

@ankith26
Copy link
Member

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Aug 13, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

coderabbitai bot commented Aug 13, 2025

Walkthrough

Adds Sound.copy() and Sound.copy to pygame.mixer.Sound with a C implementation that clones a Sound from its raw buffer (preserving volume), updates type stubs and docs, adjusts mixer cleanup to halt groups, and adds tests verifying copy parity, independence, playback, and subclass preservation.

Changes

Cohort / File(s) Summary
API stubs
buildconfig/stubs/pygame/mixer.pyi
Import Self from typing_extensions and add Sound.copy() -> Self and Sound.__copy__() -> Self.
C implementation & runtime
src_c/mixer.c
Add snd_copy (static PyObject *), forward-declare snd_get_arraystruct, implement buffer-based copy preserving Mix_Chunk volume, wire copy and __copy__ into sound_methods, and change mixer cleanup to halt groups via Mix_HaltGroup.
Documentation
docs/reST/ref/mixer.rst, src_c/doc/mixer_doc.h
Add DOC_MIXER_SOUND_COPY macro and documentation entries for Sound.copy/__copy__; note that bytes(Sound)/bytearray(Sound) use the buffer interface. (Doc text inserted in two locations.)
Tests
test/mixer_test.py
Add test_snd_copy (imports copy), testing multiple formats and asserting length, channels, volume parity and independence, raw-data equality, playback after original deletion, and subclass copy preservation.

Sequence Diagram(s)

sequenceDiagram
  participant Py as Python
  participant Sound as mixer.Sound (caller)
  participant C as mixer.c (snd_copy)
  participant Raw as raw buffer
  participant New as New Sound

  Py->>Sound: call copy() / __copy__()
  Sound->>C: invoke snd_copy(self)
  C->>Sound: snd_get_raw(self)
  Sound-->>Raw: return buffer/bytes
  C->>New: allocate new Sound (tp_new)
  C->>New: initialize from buffer via sound_init
  C->>New: copy Mix_Chunk volume from source
  C-->>Py: return New
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

I hop and press the copy key,
Two sounds now hum and follow me.
One keeps volume, one stays true,
Subclass echoes — fresh and new.
Rabbit claps — a twin for you! 🐇🎶

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
buildconfig/stubs/pygame/mixer.pyi (1)

75-76: Use Self as the return type to preserve subclass typing for copy/copy

Across pygame stubs, copy() methods usually return Self (see Surface, Rect, etc.). For consistency and better type inference when subclassing Sound, prefer Self here. If you keep returning Sound, copying a subclass will be typed as base Sound.

Apply this diff:

-from typing_extensions import (
-    Buffer,  # collections.abc 3.12
-    deprecated,  # added in 3.13
-)
+from typing_extensions import (
+    Buffer,  # collections.abc 3.12
+    deprecated,  # added in 3.13
+    Self,
+)
@@
-    def copy(self) -> Sound: ...
-    def __copy__(self) -> Sound: ...
+    def copy(self) -> Self: ...
+    def __copy__(self) -> Self: ...
docs/reST/ref/mixer.rst (1)

507-523: Clarify subclass behavior and add copy/deepcopy note if applicable

If we keep or change the implementation to preserve subclass type on copy (recommended), consider adding a sentence: “The returned object has the same class as the original.” If implementing deepcopy to delegate to copy(), mirror the Surface docs and add that note here as well.

Proposed doc tweak (only if subclass type is preserved):

-      Return a new Sound object that is a deep copy of this Sound. The new Sound
-      will be just as if you loaded it from the same file on disk as you did the
-      original Sound. If the copy fails, a ``TypeError`` or :meth:`pygame.error`
-      exception will be raised.
+      Return a new Sound object that is a deep copy of this Sound. The returned
+      object will have the same class as the original and be just as if you
+      loaded it from the same file on disk. If the copy fails, a ``TypeError``
+      or :meth:`pygame.error` exception will be raised.
src_c/mixer.c (1)

895-897: Consider supporting deepcopy for parity with Surface and Python’s copy protocol

Optional but nice-to-have: implement deepcopy to delegate to copy(), like Surface does, so copy.copy() and copy.deepcopy() both work intuitively.

Add this wrapper function (outside current hunk):

// Add near snd_copy
static PyObject *
snd_deepcopy(PyObject *self, PyObject *memo)
{
    // ignore memo, perform a deep copy identical to copy()
    return snd_copy(self, NULL);
}

Then add this entry to sound_methods:

     {"copy", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
     {"__copy__", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
+    {"__deepcopy__", snd_deepcopy, METH_O, DOC_MIXER_SOUND_COPY},

And update the stubs and docs accordingly, if adopted.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94370b6 and 88a9a13.

📒 Files selected for processing (5)
  • buildconfig/stubs/pygame/mixer.pyi (1 hunks)
  • docs/reST/ref/mixer.rst (2 hunks)
  • src_c/doc/mixer_doc.h (1 hunks)
  • src_c/mixer.c (3 hunks)
  • test/mixer_test.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
buildconfig/stubs/pygame/mixer.pyi (7)
buildconfig/stubs/pygame/sprite.pyi (1)
  • copy (142-142)
buildconfig/stubs/pygame/surface.pyi (1)
  • copy (335-346)
buildconfig/stubs/pygame/math.pyi (1)
  • copy (62-62)
buildconfig/stubs/pygame/mask.pyi (1)
  • copy (21-21)
buildconfig/stubs/pygame/cursors.pyi (1)
  • copy (83-83)
buildconfig/stubs/pygame/geometry.pyi (2)
  • copy (138-138)
  • copy (175-175)
buildconfig/stubs/pygame/rect.pyi (1)
  • copy (152-152)
test/mixer_test.py (3)
src_c/mixer.c (3)
  • mixer (2088-2179)
  • get_num_channels (1455-1460)
  • get_busy (1509-1517)
buildconfig/stubs/pygame/mixer.pyi (20)
  • init (16-23)
  • get_sdl_mixer_version (46-46)
  • Sound (48-76)
  • copy (75-75)
  • get_length (73-73)
  • get_num_channels (40-40)
  • get_num_channels (72-72)
  • get_volume (71-71)
  • get_volume (99-99)
  • get_raw (74-74)
  • set_volume (70-70)
  • set_volume (96-96)
  • set_volume (98-98)
  • play (53-58)
  • play (82-88)
  • get_busy (45-45)
  • get_busy (100-100)
  • stop (35-35)
  • stop (68-68)
  • stop (89-89)
test/test_utils/__init__.py (1)
  • example_path (36-37)
🪛 Flake8 (7.2.0)
test/mixer_test.py

[error] 1350-1350: local variable 'e' is assigned to but never used

(F841)

🔇 Additional comments (4)
src_c/doc/mixer_doc.h (1)

29-29: Docs addition looks good and matches the new API

The macro clearly documents both Sound.copy() and copy.copy(original_sound), aligned with the implementation.

test/mixer_test.py (1)

1333-1372: Good coverage of Sound.copy parity and independence

The test exercises core invariants: length, channels, volume, raw bytes parity; volume independence; basic playback on the copy; and conditional MP3 coverage. Looks solid.

docs/reST/ref/mixer.rst (1)

372-375: Nice clarification about bytes(Sound) and buffer interface

This note preempts confusion about bytes and explains the existing buffer interface well.

src_c/mixer.c (1)

92-94: Forward declaration is fine

Adding the forward declaration fixes ordering and avoids compiler warnings when referencing the getter in sound_getset.

@oddbookworm
Copy link
Member Author

@coderabbitai please review the changes now

Copy link
Contributor

coderabbitai bot commented Aug 13, 2025

Tip

For best results, initiate chat on the files or code changes.

@oddbookworm I'll provide a comprehensive review of your Sound.copy implementation. This is a well-structured PR that adds copy functionality to pygame.mixer.Sound objects.

🎯 Overall Assessment

Excellent implementation! The PR demonstrates solid software engineering practices with comprehensive coverage across type stubs, documentation, C implementation, and tests.

📋 Detailed Review

Type Stubs (buildconfig/stubs/pygame/mixer.pyi)

The type annotations are correct and follow Python typing conventions:

def copy(self) -> Sound: ...
def __copy__(self) -> Sound: ...

C Implementation (src_c/mixer.c)

The snd_copy implementation is robust:

  • Properly handles memory allocation and error cases
  • Uses snd_get_raw to access the underlying buffer data
  • Raises appropriate SDLError on failure
  • Correctly implements both copy() and __copy__() methods

Documentation (docs/reST/ref/mixer.rst)

Well-documented with:

  • Clear method signature
  • Proper version information (versionadded 2.5.6)
  • Examples showing both direct call and copy.copy() usage
  • Appropriate exception documentation

Tests (test/mixer_test.py)

Comprehensive test coverage that:

  • Tests multiple audio formats (including conditional MP3 support)
  • Verifies independence between original and copy
  • Tests all key properties (length, channels, volume, raw data)
  • Includes playback verification
  • Gracefully handles unsupported formats

🔍 Minor Observations

  1. Documentation duplication: The copy method documentation appears twice in the RST file - this might be intentional for different sections, but worth verifying.

  2. Error handling: The C implementation properly raises SDLError when copy fails, which aligns with pygame conventions.

  3. Memory management: The implementation correctly handles the buffer data transfer without memory leaks.

🏆 Strengths

  • Complete implementation: Covers all necessary components
  • Python protocol compliance: Properly implements __copy__ for standard library compatibility
  • Robust error handling: Appropriate exception handling throughout
  • Thorough testing: Tests cover edge cases and verify independence
  • Good documentation: Clear usage examples and version information

🎉 Recommendation

APPROVED - This is a high-quality implementation that adds valuable functionality while maintaining code quality standards. The deep copy semantics are well-implemented and properly tested.

Great work on this comprehensive addition to pygame-ce! 🐰

  🐰
 /   \
( ^.^ )
 > ^ <

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
src_c/mixer.c (1)

298-305: Refcount leak in endsound_callback: missing DECREF of current sound

When a channel finishes or dequeues to the next sound, channeldata[channel].sound is reassigned or set to NULL without a corresponding Py_XDECREF, leaking a reference per playback.

Apply this diff:

         if (channeldata[channel].queue) {
             PyGILState_STATE gstate = PyGILState_Ensure();
             int channelnum;
             Mix_Chunk *sound = pgSound_AsChunk(channeldata[channel].queue);
+            Py_XDECREF(channeldata[channel].sound);
             channeldata[channel].sound = channeldata[channel].queue;
             channeldata[channel].queue = NULL;
             PyGILState_Release(gstate);
             channelnum = Mix_PlayChannelTimed(channel, sound, 0, -1);
             if (channelnum != -1) {
                 Mix_GroupChannel(channelnum, (int)(intptr_t)sound);
             }
         }
         else {
             PyGILState_STATE gstate = PyGILState_Ensure();
+            Py_XDECREF(channeldata[channel].sound);
             channeldata[channel].sound = NULL;
             PyGILState_Release(gstate);
             Mix_GroupChannel(channel, -1);
         }

Also applies to: 311-315

♻️ Duplicate comments (2)
src_c/mixer.c (2)

810-812: Fix subclass-unsafe tp_new call: pass Py_TYPE(self) to tp_new, and NULL-check the allocation

Using the subclass’s tp_new with the base type argument forces base-type allocation and can drop subclass type. Also, check allocation failure.

Apply this diff:

-    pgSoundObject *newSound =
-        (pgSoundObject *)Py_TYPE(self)->tp_new(&pgSound_Type, NULL, NULL);
+    pgSoundObject *newSound =
+        (pgSoundObject *)Py_TYPE(self)->tp_new(Py_TYPE(self), NULL, NULL);
+    if (newSound == NULL) {
+        // tp_new has already set an exception
+        return NULL;
+    }

813-836: Handle allocation failures, simplify kwargs insertion, and fix newSound leak on error paths

  • kwargs is not checked for NULL (OOM).
  • Creating a separate key object is unnecessary; use PyDict_SetItemString.
  • On errors (bytes == NULL or dict insertion failure), newSound is leaked; DECREF it.

Apply this diff:

-    PyObject *kwargs = PyDict_New();
-    PyObject *key = PyUnicode_FromString("buffer");
-    PyObject *bytes = snd_get_raw(self, NULL);
+    PyObject *kwargs = PyDict_New();
+    if (kwargs == NULL) {
+        Py_DECREF(newSound);
+        return NULL;
+    }
+    PyObject *bytes = snd_get_raw(self, NULL);
     if (bytes == NULL) {
-        // exception set already by PyBytes_FromStringAndSize
+        // exception set already by snd_get_raw / PyBytes_FromStringAndSize
         PG_SAVE_EXCEPTION
-        Py_DECREF(key);
         Py_DECREF(kwargs);
+        Py_DECREF(newSound);
         PG_UNSAVE_EXCEPTION
         return NULL;
     }
 
-    if (PyDict_SetItem(kwargs, key, bytes) < 0) {
+    if (PyDict_SetItemString(kwargs, "buffer", bytes) < 0) {
         // exception set already
         PG_SAVE_EXCEPTION
-        Py_DECREF(key);
         Py_DECREF(bytes);
         Py_DECREF(kwargs);
+        Py_DECREF(newSound);
         PG_UNSAVE_EXCEPTION
         return NULL;
     }
-    Py_DECREF(key);
     Py_DECREF(bytes);
🧹 Nitpick comments (1)
src_c/mixer.c (1)

907-909: Method wiring LGTM; consider adding a subclass retention test

copy and copy entries are correctly exposed and share the same implementation/doc. Given the tp_new fix above, please add a regression test ensuring that copying a subclass of Sound returns an instance of the same subclass.

Example test sketch:

  • Define a trivial subclass of pygame.mixer.Sound
  • Construct from a small in-memory buffer
  • Assert type(copy()) is the subclass
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88a9a13 and 12c9952.

📒 Files selected for processing (2)
  • src_c/mixer.c (3 hunks)
  • test/mixer_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/mixer_test.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-13T12:39:19.107Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • src_c/mixer.c
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: AMD64
  • GitHub Check: x86
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: x86_64
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: aarch64
  • GitHub Check: i686
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: dev-check
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: build (macos-14)
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: build (windows-latest)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: build (ubuntu-22.04)
🔇 Additional comments (1)
src_c/mixer.c (1)

92-93: Forward declaration looks fine

The forward declaration for snd_get_arraystruct matches the implementation signature below and avoids implicit declaration warnings. No issues.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
buildconfig/stubs/pygame/mixer.pyi (1)

75-76: Return Self for copy/copy to reflect subtype-preserving semantics

The runtime implementation preserves the original subclass type by using Py_TYPE(self)->tp_new(...). The stubs should model that by returning Self instead of Sound.

Apply this diff:

-    def copy(self) -> Sound: ...
-    def __copy__(self) -> Sound: ...
+    def copy(self) -> Self: ...
+    def __copy__(self) -> Self: ...

And add Self to the imports (outside this hunk), for example:

from typing_extensions import Buffer, deprecated, Self  # add Self
test/mixer_test.py (2)

1352-1356: Ensure all filenames are exercised even when MP3 is added

zip(filenames, old_volumes, new_volumes) truncates to the shortest sequence. When MP3 is appended, the last file isn’t tested. Iterate by index or cycle volumes so every file is covered.

One simple refactor:

-        for f, old_vol, new_vol in zip(filenames, old_volumes, new_volumes):
-            filename = example_path(os.path.join("data", f))
+        for i, f in enumerate(filenames):
+            filename = example_path(os.path.join("data", f))
+            old_vol = old_volumes[i % len(old_volumes)]
+            new_vol = new_volumes[i % len(new_volumes)]

Apply the same change to the subclass loop below.


1333-1409: Solid coverage of copy semantics and independence

  • Validates length, channels, volume, and raw data parity between original and copy.
  • Confirms independent volume after mutation.
  • Exercises playback on the copy and ensures channel accounting is sane.
  • Extends coverage to a Sound subclass.

Consider adding a quick check for copy.copy(sound) to exercise __copy__ as well.

Example addition outside this hunk:

import copy
# ...
cpy = copy.copy(sound)
self.assertIsInstance(cpy, mixer.Sound)
self.assertEqual(sound.get_raw(), cpy.get_raw())
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 12c9952 and 9f82126.

📒 Files selected for processing (5)
  • buildconfig/stubs/pygame/mixer.pyi (1 hunks)
  • docs/reST/ref/mixer.rst (2 hunks)
  • src_c/doc/mixer_doc.h (1 hunks)
  • src_c/mixer.c (4 hunks)
  • test/mixer_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src_c/doc/mixer_doc.h
  • docs/reST/ref/mixer.rst
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-13T12:39:19.107Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • test/mixer_test.py
  • buildconfig/stubs/pygame/mixer.pyi
  • src_c/mixer.c
🧬 Code Graph Analysis (1)
test/mixer_test.py (3)
buildconfig/stubs/pygame/mixer.pyi (17)
  • Sound (48-76)
  • init (16-23)
  • set_volume (70-70)
  • set_volume (96-96)
  • set_volume (98-98)
  • copy (75-75)
  • get_length (73-73)
  • get_num_channels (40-40)
  • get_num_channels (72-72)
  • get_volume (71-71)
  • get_volume (99-99)
  • get_raw (74-74)
  • play (53-58)
  • play (82-88)
  • stop (35-35)
  • stop (68-68)
  • stop (89-89)
test/test_utils/__init__.py (1)
  • example_path (36-37)
buildconfig/stubs/pygame/base.pyi (1)
  • error (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Debian (Bookworm - 12) [build - armv7, test - armv6]
  • GitHub Check: x86_64
  • GitHub Check: arm64
🔇 Additional comments (4)
src_c/mixer.c (4)

92-94: LGTM: forward declaration is fine

The static forward declaration for snd_get_arraystruct is appropriate and matches the later definition.


852-860: Nice touch: copy preserves the original chunk’s volume

Mirroring the source volume onto the new chunk maintains parity and matches user expectations. Good call.


861-863: Confirm refcounting: no extra INCREF should be needed when returning newSound

Returning the result of tp_new with refcount 1 is the normal pattern; an extra INCREF here would leak. However, a prior debug build report mentioned undercount on 3.14 rc. Please re-validate on your target build to ensure no premature deallocation occurs before merging.

If you still observe undercounting with a Python debug build:

  • Share a minimal reproducer that drops the copy too early.
  • We can revisit whether an INCREF is warranted in this specific code path.

922-924: Public API surface looks correct

Exposing both copy() and __copy__() to back the Python copy protocol is consistent and ergonomic.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src_c/mixer.c (1)

520-527: Fix refcount leak in mixer_quit: DECREF channeldata[i].sound

We halt groups and DECREF queue, but never decref the per-channel Python Sound reference. This leaks one Python ref per channel on quit.

Apply this diff:

                 if (channeldata[i].queue) {
                     Mix_HaltGroup(
                         (int)(intptr_t)pgSound_AsChunk(channeldata[i].sound));
                 }
                 else {
                     Mix_HaltGroup(-1);
                 }
+                Py_XDECREF(channeldata[i].sound);
                 Py_XDECREF(channeldata[i].queue);

Optional nit: Mix_HaltGroup(-1) inside the loop repeats work N times. Consider hoisting it outside the loop for the non-queue case.

🧹 Nitpick comments (2)
src_c/mixer.c (1)

810-879: Avoid double-copying large audio buffers (optional)

Current flow copies audio data twice: (1) Sound -> PyBytes, (2) PyBytes -> new Mix_Chunk memory. If you want to optimize large-copy paths, you could allocate the destination chunk and memcpy directly from orig->abuf. That would keep deep-copy semantics but with one fewer allocation/copy.

If you want, I can sketch a variant that bypasses the intermediate PyBytes and calls a small helper parallel to _chunk_from_buf.

test/mixer_test.py (1)

1334-1441: Thorough coverage; consider guarding playback asserts under “disk” driver and using subTests

Great validation across formats, method paths (copy() and copy.copy()), and subclass preservation. Two small improvements to reduce flakiness and improve diagnostics:

  • The “disk” audio driver can make get_busy() assertions unreliable. Guard the playback assertions when SDL_AUDIODRIVER=disk (similar to other tests).
  • Use subTest per file to pinpoint failures faster.

Apply this minimal guard in each playback block:

-            channel = sound_copy.play()
+            channel = sound_copy.play()
             if channel is None:
                 continue
+            if os.environ.get("SDL_AUDIODRIVER") == "disk":
+                continue
             self.assertTrue(channel.get_busy())

Optionally wrap each (f, old_vol, new_vol) iteration with self.subTest(file=f) for clearer failure reporting.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f82126 and eae8383.

📒 Files selected for processing (3)
  • buildconfig/stubs/pygame/mixer.pyi (2 hunks)
  • src_c/mixer.c (4 hunks)
  • test/mixer_test.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • buildconfig/stubs/pygame/mixer.pyi
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-13T12:39:19.107Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • test/mixer_test.py
  • src_c/mixer.c
🧬 Code Graph Analysis (1)
test/mixer_test.py (3)
buildconfig/stubs/pygame/mixer.pyi (18)
  • copy (76-76)
  • Sound (49-77)
  • init (17-24)
  • get_sdl_mixer_version (47-47)
  • set_volume (71-71)
  • set_volume (97-97)
  • set_volume (99-99)
  • get_length (74-74)
  • get_num_channels (41-41)
  • get_num_channels (73-73)
  • get_volume (72-72)
  • get_volume (100-100)
  • get_raw (75-75)
  • play (54-59)
  • play (83-89)
  • stop (36-36)
  • stop (69-69)
  • stop (90-90)
test/test_utils/__init__.py (1)
  • example_path (36-37)
buildconfig/stubs/pygame/base.pyi (1)
  • error (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: arm64 deps
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: i686
  • GitHub Check: build (macos-14)
  • GitHub Check: aarch64
  • GitHub Check: x86_64
  • GitHub Check: AMD64
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: dev-check
  • GitHub Check: x86
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: build (ubuntu-22.04)
🔇 Additional comments (4)
src_c/mixer.c (3)

92-93: Forward declaration is appropriate

Adding the static forward declaration for snd_get_arraystruct resolves ordering/visibility and avoids implicit-int-style warnings. Looks good.


810-879: Sound.copy implementation is correct and robust

Highlights:

  • Preserves subclass type via Py_TYPE(self)->tp_new — good.
  • Proper NULL checks and full error-path cleanup (no leaks).
  • Uses snd_get_raw + sound_init(buffer=...) to deep-copy bytes — simple and consistent.
  • Preserves original chunk volume on the copy — nice parity touch.
  • Correctly returns the new reference without an extra INCREF.

Re: refcounting — returning (PyObject *)newSound after tp_new + successful init is correct; adding a Py_INCREF here would leak a reference.


938-940: API surface wiring for copy/copy looks correct

Method table exports both .copy() and copy to Python with the shared implementation and appropriate doc macro. Good addition and Python protocol compliance.

test/mixer_test.py (1)

1-1: LGTM: needed import for copy.copy

Import is required for exercising copy via copy.copy(). All good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src_c/mixer.c (1)

520-528: Leak: channeldata[i].sound is never decref’d in mixer_quit

The loop decrefs queue but not sound, leaking a Python ref per channel. Add a DECREF for channeldata[i].sound on each iteration.

Apply this minimal fix:

                 if (channeldata[i].queue) {
                     Mix_HaltGroup(
                         (int)(intptr_t)pgSound_AsChunk(channeldata[i].sound));
                 }
                 else {
                     Mix_HaltGroup(-1);
                 }
+                Py_XDECREF(channeldata[i].sound);
                 Py_XDECREF(channeldata[i].queue);

Optional nit: calling Mix_HaltGroup(-1) once before the loop (instead of in every else) avoids redundant work. I can draft that change if you’d like.

🧹 Nitpick comments (1)
src_c/mixer.c (1)

816-886: Sound.copy implementation: preserves subclass, propagates errors, and keeps volume — LGTM

Highlights:

  • Constructs via Py_TYPE(self)->tp_new to preserve subclass type.
  • Robust error handling and cleanup across all failure paths (including snd_get_raw and PyDict_SetItemString).
  • Calls sound_init with a buffer kwarg; exceptions are propagated (good use of PG_SAVE/UNSAVE).
  • Copies Mix_Chunk volume from original to copy to maintain parity.
  • Returns a new reference without leaking.

Minor nit only: consider renaming dict to kwargs for readability since it’s used as keyword args for sound_init. No functional change needed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eae8383 and b3ff4cc.

📒 Files selected for processing (1)
  • src_c/mixer.c (7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-13T12:39:19.107Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • src_c/mixer.c
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: build (macos-14)
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: x86_64
  • GitHub Check: i686
  • GitHub Check: build (windows-latest)
  • GitHub Check: aarch64
  • GitHub Check: x86
  • GitHub Check: AMD64
  • GitHub Check: dev-check
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
🔇 Additional comments (4)
src_c/mixer.c (4)

92-93: Forward declaration for snd_get_arraystruct looks correct

Prototype matches the definition later; unblocks usage in sound_getset. No issues.


291-319: Switch to Py_DECREF in endsound_callback is safe here

Given queue is only non-NULL when a sound is currently playing (and callback fires on channel end), channeldata[channel].sound should never be NULL in either branch. Using Py_DECREF avoids silently ignoring logic errors. Good tightening.

If you want to be extra defensive in debug builds, consider an assertion before Py_DECREF to document this invariant.


944-946: Expose both copy() and copy() in the method table — good API ergonomics

This aligns with Python’s copy protocol and keeps the direct method. Ensure docs/stubs reflect Self returns (which your PR notes indicate). Looks good.


681-685: Confirmed: Mix_HaltGroup performs synchronous DECREF via endsound_callback

Verification of the SDL_mixer documentation shows that Mix_HaltChannel (and by extension Mix_HaltGroup) invokes any registered Mix_ChannelFinished callback for halted channels before the halt call returns, running under the audio lock. This callback (endsound_callback) will perform the DECREF, so removing the direct DECREF here is safe and will not leak or race.

No code changes needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src_c/mixer.c (1)

522-529: Leak on shutdown: channeldata[i].sound is never decref’d (and redundant group halts in loop)

The loop DECREFs queue but not sound. This leaks one Python ref per channel on mixer quit. Also, calling Mix_HaltGroup(-1) inside the loop repeats the same global halt N times (minor perf nit).

Apply at least the DECREF fix; consider hoisting the global halt outside the loop.

Proposed diff:

                 if (channeldata[i].queue) {
                     Mix_HaltGroup(
                         (int)(intptr_t)pgSound_AsChunk(channeldata[i].sound));
                 }
                 else {
                     Mix_HaltGroup(-1);
                 }
+                Py_XDECREF(channeldata[i].sound);
                 Py_XDECREF(channeldata[i].queue);

Nit (optional): Move Mix_HaltGroup(-1) out of the loop and call it once for the non-queue case.

🧹 Nitpick comments (1)
src_c/mixer.c (1)

302-314: Switch to Py_DECREF assumes non-NULL; safeguard or assert invariant

endsound_callback likely runs only when a Sound was playing, so channeldata[channel].sound should be non-NULL. If that invariant can ever be violated (edge cases, future changes), Py_DECREF(NULL) will crash.

If you want robustness without sacrificing intent, either add an assert or use Py_XDECREF here.

Proposed diff:

-            Py_DECREF(channeldata[channel].sound);
+            Py_XDECREF(channeldata[channel].sound);
...
-            Py_DECREF(channeldata[channel].sound);
+            Py_XDECREF(channeldata[channel].sound);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b3ff4cc and 4e11fad.

📒 Files selected for processing (1)
  • src_c/mixer.c (7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-13T12:39:19.107Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • src_c/mixer.c
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: AMD64
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: aarch64
  • GitHub Check: x86_64
  • GitHub Check: i686
  • GitHub Check: dev-check
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: build (macos-14)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
🔇 Additional comments (3)
src_c/mixer.c (3)

92-93: Good forward declaration to satisfy prototype usage

Adding the snd_get_arraystruct prototype avoids implicit declarations when referenced earlier (e.g., in getset) and keeps C99/C11 compilers happy.


816-885: snd_copy: Solid implementation (subclass-preserving, safe error paths, preserves volume)

Highlights:

  • Preserves subclass type via Py_TYPE(self)->tp_new.
  • Robust NULL/error checks with proper cleanup and exception preservation.
  • Uses snd_get_raw + sound_init and then mirrors the original chunk’s volume onto the copy.

Returning newSound without an extra INCREF is correct — tp_new returns a new reference and tp_init does not consume it.


944-946: Method table wiring for copy and copy looks good

Exposes both copy and __copy__ backed by the same implementation with proper doc macro.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src_c/mixer.c (2)

302-306: Double-check DECREF safety in endsound_callback

These paths now unconditionally Py_DECREF(channeldata[channel].sound). In practice, this should always be non-NULL when the callback fires, but if an edge case slips through, this will crash in release builds.

  • If you want the safety of guarding against unexpected NULL without changing semantics, prefer Py_XDECREF here; or
  • Keep Py_DECREF but add an assert to catch invariants in debug builds.

Apply this minimal safety tweak if you prefer robustness over strictness:

-            Py_DECREF(channeldata[channel].sound);
+            Py_XDECREF(channeldata[channel].sound);

And in the else-path:

-            Py_DECREF(channeldata[channel].sound);
+            Py_XDECREF(channeldata[channel].sound);

Also applies to: 312-317


829-839: Nit: kwargs naming

dict here is acting as kwargs for sound_init. Consider renaming to kwargs for clarity, in line with surrounding code.

-    PyObject *dict = PyDict_New();
+    PyObject *kwargs = PyDict_New();

And adjust subsequent references accordingly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4e11fad and 0a65766.

📒 Files selected for processing (1)
  • src_c/mixer.c (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-13T12:39:19.107Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • src_c/mixer.c
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: aarch64
  • GitHub Check: x86_64
  • GitHub Check: i686
  • GitHub Check: dev-check
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: arm64
  • GitHub Check: x86_64
  • GitHub Check: x86
  • GitHub Check: AMD64
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
🔇 Additional comments (7)
src_c/mixer.c (7)

92-93: Forward declaration looks good

Declaring snd_get_arraystruct before use eliminates prototype warnings and keeps the getset table cleanly typed.


818-827: Subclass-preserving allocation and error handling: LGTM

Using Py_TYPE(self)->tp_new preserves subclass type. The NULL checks and error setting are correct and defensive.


840-858: Robust error paths on snd_get_raw and dict population

Good use of PG_SAVE_EXCEPTION/PG_UNSAVE_EXCEPTION and proper DECREF on all failure paths. Using PyDict_SetItemString avoids a temporary key allocation.


861-869: Propagate sound_init errors correctly

The code cleanly propagates sound_init’s exception and avoids masking with a secondary error. DECREF ordering is correct.


870-877: Nice: preserve original chunk volume

Mirroring the chunk volume keeps copy() semantically aligned with the original Sound state. Good call.


879-881: Refcount on return: please re-verify whether an extra INCREF is required

Returning a newly created PyObject normally should not require an extra Py_INCREF. However, previous discussion on this PR mentioned undercounting observed with a debug build (Py 3.14.0rc1) unless an extra INCREF was added in snd_copy. Current code returns without INCREF.

Before we lock this in, can you re-run the failing scenario you had previously and confirm that no premature deallocation occurs with the current implementation? If it still undercounts, we need to add a balanced INCREF; if not, we should keep it as-is to avoid a leak.

If verification indicates undercounting, apply:

-    Py_DECREF(dict);
-    return (PyObject *)newSound;
+    Py_DECREF(dict);
+    Py_INCREF(newSound);  // Only if undercount is reproducible; otherwise omit
+    return (PyObject *)newSound;

Please share the exact reproducer (Python version, debug flags, minimal script) if you still observe the undercount so we can document the rationale in a code comment.


940-942: Method registrations for copy and copy look correct

Both methods mapped to the same implementation with shared docs; aligns with Python’s copy protocol.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src_c/mixer.c (1)

522-529: Leak: channeldata[i].sound and .queue are never DECREF’d during mixer_quit

The loop halts groups but does not release the Python references stored in channeldata[i].sound and channeldata[i].queue. This leaks one ref per non-NULL field per channel on quit.

Apply this minimal fix to release those references and null the fields:

                 if (channeldata[i].sound) {
                     Mix_HaltGroup(
                         (int)(intptr_t)pgSound_AsChunk(channeldata[i].sound));
+                    Py_XDECREF(channeldata[i].sound);
+                    channeldata[i].sound = NULL;
                 }
                 if (channeldata[i].queue) {
                     Mix_HaltGroup(
                         (int)(intptr_t)pgSound_AsChunk(channeldata[i].queue));
+                    Py_XDECREF(channeldata[i].queue);
+                    channeldata[i].queue = NULL;
                 }

Optional: Calling Mix_HaltGroup(-1) or Mix_HaltChannel(-1) once outside the loop would avoid repeated halts and improve efficiency, but the above is sufficient to fix the leak.

🧹 Nitpick comments (2)
src_c/mixer.c (2)

812-881: snd_copy: Great error handling, subclass preservation, and volume parity; please re-verify refcount under 3.14 debug; consider copying Python-level attrs

  • Good: Uses Py_TYPE(self)->tp_new to preserve subclass; robust NULL checks and cleanup; propagates exceptions; mirrors Mix_Chunk volume.
  • Refcount: Returning without an extra Py_INCREF(newSound) is the standard pattern (tp_new returns a new reference, and returning from the C method transfers it). Given earlier reports of “undercount” on Python 3.14 debug, please re-run under that environment to confirm behavior. If an extra INCREF is empirically required, add it with a brief comment explaining why to avoid regressions.

Optional: If users subclass Sound and add Python-level attributes, shallow-copying dict preserves that state on copies.

Apply this (optional) diff just before Py_DECREF(dict):

-    Py_DECREF(dict);
+    /* Best-effort: copy Python-level attributes if present on a subclass. */
+    {
+        PyObject *src_dict = PyObject_GetAttrString(self, "__dict__");
+        if (src_dict) {
+            PyObject *dst_dict =
+                PyObject_GetAttrString((PyObject *)newSound, "__dict__");
+            if (dst_dict) {
+                if (PyDict_Update(dst_dict, src_dict) < 0) {
+                    /* Don't make copy fail due to attribute copy; clear and continue. */
+                    PyErr_Clear();
+                }
+                Py_DECREF(dst_dict);
+            }
+            else {
+                PyErr_Clear();
+            }
+            Py_DECREF(src_dict);
+        }
+        else {
+            PyErr_Clear();
+        }
+    }
+    Py_DECREF(dict);
     return (PyObject *)newSound;

940-942: Consider adding deepcopy to integrate with copy.deepcopy

You’ve implemented copy() and copy() — nice. For completeness with Python’s copy protocol, consider adding deepcopy(self, memo) that delegates to the same implementation. It prevents surprising behavior when users call copy.deepcopy(sound).

Proposed table entry (insert alongside the others):

     {"copy", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
     {"__copy__", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
+    {"__deepcopy__", (PyCFunction)snd_deepcopy, METH_O, DOC_MIXER_SOUND_COPY},

And add this simple wrapper (anywhere near snd_copy):

static PyObject *snd_deepcopy(PyObject *self, PyObject *memo) {
    /* Deep-copy semantics are identical for Sound; memo is unused. */
    return snd_copy(self, NULL);
}

If you adopt this, remember to update stubs/docs accordingly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0a65766 and 669f06e.

📒 Files selected for processing (1)
  • src_c/mixer.c (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-13T12:39:19.107Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.107Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • src_c/mixer.c
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: arm64
  • GitHub Check: x86_64
  • GitHub Check: aarch64
  • GitHub Check: x86_64
  • GitHub Check: i686
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: x86
  • GitHub Check: AMD64
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
🔇 Additional comments (1)
src_c/mixer.c (1)

92-94: Forward declaration for snd_get_arraystruct looks correct

Prototype matches the implementation below. No concerns.

@oddbookworm
Copy link
Member Author

@ankith26 comments addressed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src_c/mixer.c (2)

868-869: Return without extra INCREF looks correct; please re-verify 3.14 debug undercount claim.

tp_new returns a new reference; sound_init does not steal it; returning newSound as-is should be correct. Adding a Py_INCREF here would leak a reference. There was prior discussion indicating an “undercount” seen with Python 3.14 debug builds; if that reproduces, it likely points elsewhere (e.g., an extra DECREF in the caller or callback paths), not here.

Can you re-run your 3.14 debug check and share a minimal reproducer if you still see undercount? I can help trace it.


523-530: Refcount leak in mixer_quit: channeldata[i].sound and .queue are never DECREF’d.

After disabling the callback (Line 519), the cleanup loop halts groups but does not release the Python references stored in channeldata. This leaks up to two references per channel on quit.

Apply DECREFs and clear the pointers:

         if (channeldata) {
             for (i = 0; i < numchanneldata; ++i) {
                 if (channeldata[i].sound) {
                     Mix_HaltGroup(
                         (int)(intptr_t)pgSound_AsChunk(channeldata[i].sound));
+                    Py_XDECREF(channeldata[i].sound);
+                    channeldata[i].sound = NULL;
                 }
                 if (channeldata[i].queue) {
                     Mix_HaltGroup(
                         (int)(intptr_t)pgSound_AsChunk(channeldata[i].queue));
+                    Py_XDECREF(channeldata[i].queue);
+                    channeldata[i].queue = NULL;
                 }
             }

Optional follow-up: Instead of halting per-chunk inside the loop, consider a single Mix_HaltChannel(-1) (or Mix_HaltGroup(-1)) before the loop and just perform the DECREFs inside the loop for O(N) → O(1) halting.

🧹 Nitpick comments (1)
src_c/mixer.c (1)

929-931: copy/copy exposure is good; consider adding deepcopy (optional).

For full interoperability with copy.deepcopy, consider adding deepcopy(self, memo) to return a shallow copy as well (Sound is effectively atomic). This avoids deepcopy falling back to slower generic paths.

Example change within this hunk:

     {"copy", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
     {"__copy__", snd_copy, METH_NOARGS, DOC_MIXER_SOUND_COPY},
+    {"__deepcopy__", (PyCFunction)snd_deepcopy, METH_O, DOC_MIXER_SOUND_COPY},

And add this helper elsewhere in the file:

static PyObject *
snd_deepcopy(PyObject *self, PyObject *memo)
{
    /* ignore memo; a Sound is effectively atomic */
    return snd_copy(self, NULL);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3902608 and e6704ea.

📒 Files selected for processing (1)
  • src_c/mixer.c (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.130Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.
📚 Learning: 2025-08-17T17:23:06.787Z
Learnt from: ankith26
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:520-529
Timestamp: 2025-08-17T17:23:06.787Z
Learning: In pygame-ce's mixer.c, when fixing reference leaks in mixer_quit, the cleanest approach is to first call Mix_ChannelFinished(NULL) to disable the callback and prevent race conditions, then manually handle Py_XDECREF calls for channeldata[i].sound and channeldata[i].queue after the Mix_HaltGroup calls.

Applied to files:

  • src_c/mixer.c
📚 Learning: 2025-08-13T12:39:19.130Z
Learnt from: oddbookworm
PR: pygame-community/pygame-ce#3556
File: src_c/mixer.c:806-836
Timestamp: 2025-08-13T12:39:19.130Z
Learning: In pygame-ce's mixer.c, the snd_copy function requires Py_INCREF(newSound) before returning to maintain proper reference counting. Without it, copied Sound objects get deallocated prematurely.

Applied to files:

  • src_c/mixer.c
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: aarch64
  • GitHub Check: i686
  • GitHub Check: x86_64
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: msys2 (clang64, clang-x86_64)
  • GitHub Check: msys2 (ucrt64, ucrt-x86_64)
  • GitHub Check: msys2 (mingw64, x86_64)
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
  • GitHub Check: dev-check
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: AMD64
  • GitHub Check: x86
🔇 Additional comments (3)
src_c/mixer.c (3)

92-93: Forward declaration for snd_get_arraystruct is fine.

Declaring the getter up front improves readability and avoids compiler warnings before its use in the getset table. No issues.


519-519: Good call: disable Mix_ChannelFinished during shutdown to avoid races.

Turning off the channel-finished callback before halting channels prevents the callback from touching channeldata after it’s being torn down. This is the right sequencing.


813-870: Sound.copy: robust, subclass-preserving, and error-safe implementation.

Highlights:

  • Uses Py_TYPE(self)->tp_new to preserve subclass types. Good.
  • Copies from get_raw buffer via keyword "buffer" and propagates errors correctly.
  • Cleans up all temporaries on each failure path.
  • Mirrors the original Mix_Chunk volume onto the copy for parity. Good API ergonomics.

No changes requested for this block.

Copy link
Member

@MyreMylar MyreMylar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK LGTM 👍

if (channeldata[i].queue) {
Mix_HaltGroup(
(int)(intptr_t)pgSound_AsChunk(channeldata[i].queue));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now these changes would need to be reverted too I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C API mixer pygame.mixer New API This pull request may need extra debate as it adds a new class or function to pygame
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Sound.copy(), Sound.__copy__(), Sound.__bytes__ methods
3 participants