SDL_mixer 3.0 (aka "SDL3_mixer") is a dramatically different library than previous versions. The API has been completely redesigned. There is no compatibility layer. If you want to use it, you have to migrate to it.
SDL3_mixer requires SDL3. It relies on many features that are new to SDL3, both internally and in the public API, so if your project is on SDL 1.2 or SDL2, you'll have to move your project to SDL3 at the same time.
That being said, we think SDL3_mixer and SDL3 are both great pieces of software--significant improvements over their previous versions--and we think that once you move to them, you'll be quite happy you did.
There are a lot of things that don't have simple replacements that can be changed mechanically to migrate to SDL3_mixer. The new API is in many ways more powerful, but also much simpler. For example, there's no equivalent of Mix_ModMusicJumpToOrder(), for example, because messing with the specifics of MOD files in the public API is both uncommon and generally pretty messy.
This migration guide will attempt to walk through the important details but it's possible that some things can't be done the same way. Feel free to open bug reports if you're totally stuck.
The end of this document provides a "tl;dr" section that lists each SDL2_mixer function name and a brief explanation about what to do with it.
Native MIDI has been removed. We have ripped this piece out of SDL2_mixer and packaged it as a separate library that can be used alongside SDL3_mixer, or without SDL_mixer at all: https://github.com/libsdl-org/SDL_native_midi
Mix_GetNumTracks(), Mix_StartTrack(), and Mix_ModMusicJumpToOrder() have been removed; these were decoder-specific APIs.
Mix_SetSoundFonts(), Mix_GetSoundFonts(), Mix_EachSoundFont(), Mix_SetTimidityCfg(), Mix_GetTimidityCfg(): these have been removed, but decoder-specific settings can be passed on in a generic way in SDL3_mixer, using SDL properties.
Mix_QuickLoad_WAV(): this was just too unsafe to offer.
The proper way to include SDL3_mixer's header is:
#include <SDL3_mixer/SDL_mixer.h>
Like SDL3, the new convention is to use <>
brackets and a subdirectory.
In SDL2_mixer, all functions started with Mix_
and macros started with MIX_
. In SDL3_mixer, everything starts with MIX_
.
Versions are now bits packed into a single int instead of a struct, using SDL3's usual magic for unpacking these.
Previously you had to tell SDL_mixer what file formats you expected to use during Mix_Init(). Now you don't:
if (!MIX_Init()) {
"MIX_Init failed: %s", SDL_GetError());
SDL_Log(else {
} "SDL_mixer is ready!");
SDL_Log( }
MIX_Init() is reference-counted; it's safe to call it more than once, and only the first call will do actual initialization tasks. Likewise, actual deinitialization with MIX_Quit() will only happen when it has been called as many times as MIX_Init() has. This allows different parts of the program to initialize and use SDL3_mixer without knowing about other parts using it too.
Previously the (singleton) mixer was opened with Mix_OpenAudio(), now one calls MIX_CreateMixerDevice(). You can open multiple mixers now, in case different parts of a program want to operate independently, or you need to use different audio devices at the same time (or, with Mix_CreateMixer(), write audio to a memory buffer instead of a device).
MIX_CreateMixerDevice() accepts an SDL_AudioSpec as a hint, but makes no promises the audio device will be at any given format. The hint is just to indicate that most of the inputs will be in a certain format, and maybe this will reduce conversion work if the device can handle it. But SDL3_mixer is well-prepared to manage any input or output formats it ends up with.
Mix_QuerySpec() has been replaced by MIX_GetMixerFormat().
SDL2_mixer made a distinction between "chunks" (uncompressed audio sitting in memory, to be played on a "channel") and "music" (streaming, probably compressed, audio). It offered multiple chunks to be mixed together, along with a single music input.
In SDL3_mixer, there is no distinction between types of audio; all inputs can be compressed or uncompressed, preloaded into memory or streamed, in any supported file format. As many as you like of any type can mix at once.
Preloaded (but not necessarily predecoded) audio is managed in MIX_Audio objects. These are audio files precached in RAM. They can be decompressed upfront, or on the fly while mixing.
In SDL2_mixer, data would be loaded with Mix_LoadWAV() for chunks, or Mix_LoadMUS() for music, or some variant thereof. In SDL3_mixer, you will call MIX_LoadAudio(), MIX_LoadAudio_IO(), or if you have some unconventional need, MIX_LoadAudioWithProperties().
A special function in SDL2_mixer, Mix_QuickLoad_RAW(), has been replaced with MIX_LoadRawAudioNoCopy(). It's a little more generic (it does not require your data be in the same format as the hardware), but it will still let you get data all the way through the mixing pipeline without any extra allocations or copies.
Many functions in SDL2_mixer had two versions: one for chunks and one for music, like Mix_FreeChunk() vs Mix_FreeMusic(). In SDL3_mixer, equivalent APIs have a single function that works with everything.
Mix_GetNumChunkDecoders() and Mix_GetChunkDecoder() (and the Music equivalents) have been replaced with MIX_GetNumAudioDecoders() and MIX_GetAudioDecoder(). The returned strings might be different than in SDL2_mixer for the same decoding backends.
SDL2_mixer had "channels," each of which contained a single chunk to be mixed during playback. Channels were referenced by index, and you would preallocate the number of channels you intended to use with Mix_AllocateChannels() (unless you needed 8 channels, the default, in which case you just used them).
SDL3_mixer has "tracks," each containing a single input (a loaded audio file, an SDL_AudioStream for dynamically generated sound, or an SDL_IOStream to stream a file on-demand). They are allocated one at a time, and referenced by the pointer returned from MIX_CreateTrack(). To keep an indexable set of tracks to match SDL2_mixer's channels, just call MIX_CreateTrack() in a loop, storing the pointers in an array, and use indices into that array. There are no tracks created by default, so to match SDL2_mixer's default, create 8 of them at startup.
Functions that return metadata about an audio file, like Mix_GetMusicTitle(), have been removed. Instead, all audio files have a set of SDL properties.
These properties offer both low-level tag information and a standard property for well-known metadata types. For example, if your MP3 has an ID3v2 tag, you might end up with a property named "SDL_mixer.metadata.id3v2.TIT2" and the same info in a standard "SDL_mixer.metadata.title" property (which there's a standard symbol for: MIX_PROP_METADATA_TITLE_STRING
. This allows SDL3_mixer to expose non-standard information, but also offer easy lookup of the most common, and most important, metadata.
After loading an audio file, MIX_GetAudioProperties() will provide the metadata (and also provide a convenient place for the app to hang their own app-specific data, as well).
SDL3_mixer offers several callbacks at different points in the mixing pipeline. They can be used to recreate SDL2_mixer's Mix_SetPostMix(), Mix_HookMusic(), Mix_HookMusicFinished(), Mix_ChannelFinished(), and Mix_RegisterEffect().
Note that in SDL3_mixer, all callbacks provide audio data in float32 format, so you won't need to have different paths for different formats.
SDL2_mixer had several "effects" that were internal code that used the Mix_RegisterEffect() interface to modify chunks before they were mixed.
Mix_SetPanning() can be replaced with MIX_SetTrackStereo(), but this will force the track to play on the Front Left and Front Right speakers in a surround-sound setup. If you actually want the track to move left to right, respecting any surround-sound layout, use MIX_SetTrack3DPosition() and move the sound across the X axis.
Mix_SetPosition() and Mix_SetDistance() can also use MIX_SetTrack3DPosition() to better effect, but you'll have to convert to 3D coordinates instead of angle/distance. Here's some (untested) math to do that:
const float radians = angle * (SDL_PI_F / 180.0f); // convert degrees to radians.
const float fdistance = ((float) distance);
const float x = SDL_cosf(radians) * fdistance; // left to right
const float y = 0.0f; // leave everything vertically centered on the listener.
const float z = SDL_sinf(radians) * fdistance; // front to back
Mix_SetReverseStereo() can be replaced with MIX_SetTrackOutputChannelMap():
const int chmap = { 1, 0 }; // left becomes right, right becomes left.
2); MIX_SetTrackOutputChannelMap(track, chmap,
In SDL2_mixer, you could "tag" channels with an int value, and then reference all channels that match that tag.
In SDL3_mixer, you can still tag a track, but the tag is an arbitrary string, like "ui" or "ambient" or whatever. A track can have multiple tags.
Tag tracks with MIX_TagTrack() and remove a tag with MIX_UntagTrack().
There are several different functions in SDL2_mixer to start a channel playing (Mix_Play*, Mix_FadeIn*, etc). All of these have collapsed into MIX_PlayTrack(). MIX_PlayTrack() takes an SDL_PropertiesID for options (if the defaults don't cover it). Before playing, a track must have an input assigned, either through MIX_SetTrackAudio() to play from preloaded audio, MIX_SetTrackIOStream() to play from a file loaded on-demand, or MIX_SetTrackAudioStream() to play procedural audio, either buffered upfront or generated on-demand.
Mix_VolumeChunk() and Mix_VolumeMusic() are replaced by MIX_SetTrackGain(). They take a float instead of an int from 0 to 128, and can be used to not only quiet the existing data, but also make it louder.
Mix_MasterVolume() is replaced by MIX_SetMasterGain(). Same idea.
Mix_HaltChannel(), MIX_HaltMusic(), Mix_FadeOutChannel() and Mix_FadeOutMusic() are replaced by MIX_StopTrack(). Be careful here: fade-outs work in sample frames of the track's input, not milliseconds, in SDL3_mixer. Use MIX_TrackMSToFrames() to convert. However, If stopping multiple tracks at once with MIX_StopAllTracks() or MIX_StopTag(), the fade out is specified in milliseconds, since we can't guarantee that all tracks have the same sample rate.
Mix_Pause(), Mix_PauseMusic(), Mix_Resume() and Mix_ResumeMusic() became MIX_PauseTrack() and MIX_ResumeTrack().
Mix_MusicDuration() became MIX_GetAudioDuration() and Mix_GetMusicPosition() became MIX_GetTrackPlaybackPosition(). MIX_SetMusicPosition() is now MIX_SetTrackPlaybackPosition().
Mix_CloseAudio() is now MIX_DestroyMixer(). Destroying a mixer will destroy everything it owns (tracks, etc), but not MIX_Audio objects, since they can be shared between mixers. MIX_Quit() can be used to destroy everything that SDL3_mixer created, including the mixers and MIX_Audio objects, which might be more convenient.
A very brief comment on what to do with each symbol in SDL2_mixer.
Some of these are listed as "no equivalent in SDL3_mixer" but could possibly be added if there is a need. If you're stuck, please file an issue and we can discuss it!