Skip to content

Create new _sdl3_mixer module#3753

Open
Starbuck5 wants to merge 4 commits into
pygame-community:mainfrom
Starbuck5:sdl3_mixer_api7
Open

Create new _sdl3_mixer module#3753
Starbuck5 wants to merge 4 commits into
pygame-community:mainfrom
Starbuck5:sdl3_mixer_api7

Conversation

@Starbuck5
Copy link
Copy Markdown
Member

Supports SDL3 mixer constructs in close to their native form. Right now it's as a private module that only builds in SDL3. This is a stepping stone towards overall mixer support for SDL3 builds, with a compatibility layer exposing old API on top of the new, along with giving folks the new powerful stuff directly.

Follow up PR after #3657 in the #3581 mega issue.

This was originally at #3692, but with so many AI reviews and merge conflicts, wanted to start fresh at a new location for ease of more review :).

Rough testing script:
import os

import pygame
import pygame._sdl3_mixer as mixer
from pygame import _audio as audio
pygame.audio = audio

data_dir = r"C:\Users\charl\Desktop\pygame-ce\examples\data"

mixer.init()

device = pygame.audio.DEFAULT_PLAYBACK_DEVICE
"""
audio.init()
devices = pygame.audio.get_playback_devices()
for device in devices:
    print(device.name)
index = int(input("Choose device index: "))\
device = devices[index]
"""

mix = mixer.Mixer(device, pygame.audio.AudioSpec(pygame.audio.U8, 2, 48000))
print(mix.spec)

audio = mixer.Audio(os.path.join(data_dir, "surfonasinewave.xm"), True, mix)
print(audio.spec)
sine_audio = mixer.Audio.from_sine_wave(1000, 0.1, mix, -1)

import sys
print(sys.getrefcount(sine_audio))

mix.play_audio(sine_audio)

del sine_audio
#print(sys.getrefcount(sine_audio))

#print(sine_audio.duration_infinite)
pygame.time.delay(3000)
#mix.play_audio(audio)

track = mixer.Track(mix)
print(track.get_remaining_frames())
track.set_audio(audio)
#track.set_filestream(os.path.join(data_dir, "surfonasinewave.xm"))
print(track.get_audio())
print(track.mixer is mix)
#track.mixer.gain = .2
track.frequency_ratio = 1.1
#track.gain = .2
track.play(max_ms= 5 * 1000)
#print(track.set_3d_position((12,3,"4")))
print(audio.get_metadata())

print(track.frames_to_ms(1000))
track.set_playback_position(track.ms_to_frames(1000))

#track.set_stereo((1.0, 0.1))

while track.playing:
    #import math
    #x = math.sin(pygame.time.get_ticks()/500)
    #z = math.cos(pygame.time.get_ticks()/500)
    #track.set_3d_position((x,0,z))
    
    #print(track.frames_to_ms(track.get_remaining_frames()) // 1000)
    #print(track.frames_to_ms(track.get_playback_position()) // 1000)

    pygame.time.wait(10)

print("Done")

#import sys
#for _ in range(10):
#    x = track.mixer
#print(sys.getrefcount(mix))
#del track

@Starbuck5 Starbuck5 requested a review from a team as a code owner March 22, 2026 07:34
@coderabbitai

This comment was marked as outdated.

@Starbuck5 Starbuck5 added New API This pull request may need extra debate as it adds a new class or function to pygame mixer pygame.mixer sdl3 labels Mar 22, 2026
coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
src_py/_sdl3_mixer.py (2)

34-43: Validate device before dereferencing _state for clearer API errors.

Right now invalid device input can raise AttributeError instead of a consistent TypeError from this wrapper.

Proposed refactor
 class Mixer(_sdl3_mixer_c.Mixer):
@@
     def __init__(
         self,
         device: audio.AudioDevice = audio.DEFAULT_PLAYBACK_DEVICE,
         spec: audio.AudioSpec | None = None,
     ) -> None:
+        if not isinstance(device, audio.AudioDevice):
+            raise TypeError(
+                "Mixer init 'device' argument must be an AudioDevice, "
+                f"received {type(device)}"
+            )
         if spec is None:
             _sdl3_mixer_c.Mixer.__init__(self, device._state, spec)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src_py/_sdl3_mixer.py` around lines 34 - 43, Validate the incoming device
before accessing device._state in Mixer.__init__ to provide a consistent
TypeError for bad inputs: in __init__ check that device is an instance of
audio.AudioDevice (or is audio.DEFAULT_PLAYBACK_DEVICE) and if not raise
TypeError with a clear message, then call _sdl3_mixer_c.Mixer.__init__(self,
device._state, ...) in both branches (the spec None branch and the spec
instanceof audio.AudioSpec branch); ensure both paths perform the same device
validation before dereferencing device._state.

109-114: Minor cleanup: collapse duplicated set_audiostream call.

Both valid branches call the same C method; this can be simplified without changing behavior.

Proposed cleanup
     def set_audiostream(self, audiostream: audio.AudioStream | None) -> None:
-        if isinstance(audiostream, audio.AudioStream):
-            _sdl3_mixer_c.Track.set_audiostream(self, audiostream)
-        elif audiostream is None:
+        if isinstance(audiostream, audio.AudioStream) or audiostream is None:
             _sdl3_mixer_c.Track.set_audiostream(self, None)
         else:
             raise TypeError(
                 "audiostream argument must be an AudioStream or None, "
                 f"received {type(audiostream)}"
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src_py/_sdl3_mixer.py` around lines 109 - 114, Collapse the duplicate calls
to _sdl3_mixer_c.Track.set_audiostream in Track.set_audiostream: instead of
calling _sdl3_mixer_c.Track.set_audiostream(self, audiostream) in both the
isinstance(audio.AudioStream) and audiostream is None branches, call
_sdl3_mixer_c.Track.set_audiostream(self, audiostream) once; keep a simple type
check to raise a TypeError for invalid values (non audio.AudioStream and
non-None) so the function still validates input.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src_py/_sdl3_mixer.py`:
- Around line 34-43: Validate the incoming device before accessing device._state
in Mixer.__init__ to provide a consistent TypeError for bad inputs: in __init__
check that device is an instance of audio.AudioDevice (or is
audio.DEFAULT_PLAYBACK_DEVICE) and if not raise TypeError with a clear message,
then call _sdl3_mixer_c.Mixer.__init__(self, device._state, ...) in both
branches (the spec None branch and the spec instanceof audio.AudioSpec branch);
ensure both paths perform the same device validation before dereferencing
device._state.
- Around line 109-114: Collapse the duplicate calls to
_sdl3_mixer_c.Track.set_audiostream in Track.set_audiostream: instead of calling
_sdl3_mixer_c.Track.set_audiostream(self, audiostream) in both the
isinstance(audio.AudioStream) and audiostream is None branches, call
_sdl3_mixer_c.Track.set_audiostream(self, audiostream) once; keep a simple type
check to raise a TypeError for invalid values (non audio.AudioStream and
non-None) so the function still validates input.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4c36b424-34c6-4605-9e73-a1e7ac65a328

📥 Commits

Reviewing files that changed from the base of the PR and between 929fa32 and 6375c3d.

📒 Files selected for processing (1)
  • src_py/_sdl3_mixer.py

Supports SDL3 mixer constructs in close to their native form. Right now it's as a private module that only builds in SDL3. This is a stepping stone towards overall mixer support for SDL3 builds, with a compatibility layer exposing old API on top of the new, along with giving folks the new powerful stuff directly.
Copy link
Copy Markdown
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 (2)
src_c/_sdl3_mixer_c.c (2)

594-601: 💤 Low value

Known MIX_CreateSineWaveAudio bug — track upstream fix.

The inline comment notes MIX_CreateSineWaveAudio is bugged (invalid context parameter). Consider opening an upstream SDL3_mixer issue (or linking the existing one) and adding a TODO with a tracking link so this can be revisited and the user-facing error surfaced clearly until then.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src_c/_sdl3_mixer_c.c` around lines 594 - 601, The call to
MIX_CreateSineWaveAudio (in the code path that assigns MIX_Audio
*sine_wave_audio) is known to be bugged and currently hides user-facing context
errors; open or link an upstream SDL3_mixer issue tracking this "invalid context
parameter" bug and add a TODO comment next to the MIX_CreateSineWaveAudio call
that includes the upstream issue URL/ID and a short note to revisit once
patched, and update the error handling so the RAISE(pgExc_SDLError,
SDL_GetError()) remains but the TODO clearly documents the upstream issue for
future removal.

1527-1528: 💤 Low value

Minor: stale function name in comment.

The comment refers to pg_audio_clear, but the function actually called is pg_mixer_clear. Likely a leftover from a rename.

Proposed fix
-    // allow pg_audio_exec to omit calling pg_audio_clear on error
+    // allow pg_audio_exec to omit calling pg_mixer_clear on error
     (void)pg_mixer_clear((PyObject *)module);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src_c/_sdl3_mixer_c.c` around lines 1527 - 1528, Update the stale comment to
reference the correct function name: change the comment that currently mentions
"pg_audio_clear" to "pg_mixer_clear" (the code calls pg_mixer_clear((PyObject
*)module)); ensure the comment accurately explains that this allows
pg_audio_exec to omit calling pg_mixer_clear on error, or replace the comment
with a concise note about allowing pg_audio_exec to omit clearing on error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src_c/_sdl3_mixer_c.c`:
- Around line 98-106: The code calls PyObject_GetAttrString(...) to fetch
_audio_type and immediately passes it to PyArg_ParseTupleAndKeywords with "O!"
which is unsafe if GetAttrString returned NULL; add a NULL check after each
PyObject_GetAttrString call (in this function and in pg_audio_obj_init,
pg_audio_obj_from_raw, pg_audio_obj_from_sine_wave, pg_track_obj_set_audio): if
the returned pointer is NULL, return NULL immediately (do not DECREF), otherwise
proceed to call PyArg_ParseTupleAndKeywords and only DECREF the acquired
audio_type after successful/failed parsing as appropriate.
- Around line 992-999: The SDL_IOStream returned by pgRWops_FromObject is not
closed when MIX_SetTrackIOStream(self->track, io, true) fails; modify the
failure path in the function handling the SDL_IOStream so that if
MIX_SetTrackIOStream(...) returns false you call SDL_CloseIO(io) before
returning the error via RAISE(pgExc_SDLError, SDL_GetError()), ensuring the
stream is freed when ownership was not transferred to SDL_mixer.

---

Nitpick comments:
In `@src_c/_sdl3_mixer_c.c`:
- Around line 594-601: The call to MIX_CreateSineWaveAudio (in the code path
that assigns MIX_Audio *sine_wave_audio) is known to be bugged and currently
hides user-facing context errors; open or link an upstream SDL3_mixer issue
tracking this "invalid context parameter" bug and add a TODO comment next to the
MIX_CreateSineWaveAudio call that includes the upstream issue URL/ID and a short
note to revisit once patched, and update the error handling so the
RAISE(pgExc_SDLError, SDL_GetError()) remains but the TODO clearly documents the
upstream issue for future removal.
- Around line 1527-1528: Update the stale comment to reference the correct
function name: change the comment that currently mentions "pg_audio_clear" to
"pg_mixer_clear" (the code calls pg_mixer_clear((PyObject *)module)); ensure the
comment accurately explains that this allows pg_audio_exec to omit calling
pg_mixer_clear on error, or replace the comment with a concise note about
allowing pg_audio_exec to omit clearing on error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0264f656-682e-4f45-a354-70806aafc36c

📥 Commits

Reviewing files that changed from the base of the PR and between 6375c3d and 0dde50f.

📒 Files selected for processing (13)
  • .github/workflows/build-sdl3.yml
  • buildconfig/download_win_prebuilt.py
  • buildconfig/stubs/meson.build
  • buildconfig/stubs/mypy_allow_list.txt
  • buildconfig/stubs/pygame/_sdl3_mixer.pyi
  • dev.py
  • meson.build
  • src_c/_base_audio.c
  • src_c/_base_audio.h
  • src_c/_sdl3_mixer_c.c
  • src_c/meson.build
  • src_py/_sdl3_mixer.py
  • src_py/meson.build
💤 Files with no reviewable changes (1)
  • dev.py
✅ Files skipped from review due to trivial changes (5)
  • src_c/_base_audio.h
  • buildconfig/stubs/mypy_allow_list.txt
  • buildconfig/stubs/meson.build
  • .github/workflows/build-sdl3.yml
  • meson.build
🚧 Files skipped from review as they are similar to previous changes (5)
  • src_py/meson.build
  • src_c/meson.build
  • src_c/_base_audio.c
  • src_py/_sdl3_mixer.py
  • buildconfig/stubs/pygame/_sdl3_mixer.pyi

Comment thread src_c/_sdl3_mixer_c.c
Comment thread src_c/_sdl3_mixer_c.c
Comment thread src_c/_sdl3_mixer_c.c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mixer pygame.mixer New API This pull request may need extra debate as it adds a new class or function to pygame sdl3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant