SDL_mixer: decoder_vorbis: Use channel maps so surround sound arrives correctly.

From 37b2f3325a0fb1e98ba265aa38826aa9e16624fb Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Fri, 6 Feb 2026 10:21:15 -0500
Subject: [PATCH] decoder_vorbis: Use channel maps so surround sound arrives
 correctly.

Reference Issue #804.
---
 src/decoder_vorbis.c | 47 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 45 insertions(+), 2 deletions(-)

diff --git a/src/decoder_vorbis.c b/src/decoder_vorbis.c
index 8abb55f5..f4dcc180 100644
--- a/src/decoder_vorbis.c
+++ b/src/decoder_vorbis.c
@@ -249,6 +249,49 @@ static bool SDLCALL VORBIS_init_track(void *audio_userdata, SDL_IOStream *io, co
     return true;
 }
 
+static void UpdateVorbisStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *spec)
+{
+    static const int vorbis_chmap_3[] = { 0, 2, 1, -1, -1, -1 };  // SDL=FL,FR,FC,LFE,RL,RR  VORBIS=FL,FC,FR
+    static const int vorbis_chmap_5[] = { 0, 2, 1, 4, 5, -1};  // SDL=FL,FR,FC,LFE,RL,RR  VORBIS=FL,FC,FR,RL,RR
+    static const int vorbis_chmap_6[] = { 0, 2, 1, 4, 5, 3 };  // SDL=FL,FR,FC,LFE,RL,RR  VORBIS=FL,FC,FR,RL,RR,LFE
+    static const int vorbis_chmap_7[] = { 0, 2, 1, 5, 6, 4, 3 };  // SDL=FL,FR,FC,LFE,RC,SL,SR  VORBIS=FL,FC,FR,SL,SR,RC,LFE
+    static const int vorbis_chmap_8[] = { 0, 2, 1, 6, 7, 4, 5, 3 };  // SDL=FL,FR,FC,LFE,RL,RR,SL,SR  VORBIS=FL,FC,FR,SL,SR,RL,RR,LFE
+
+    SDL_AudioSpec bumped_spec;
+    const int *chmap = NULL;
+    switch (spec->channels) {
+        // 3 and 5 channel audio offers a Front Center channel, which SDL doesn't have until 5.1 (6 channels), so bump the spec
+        //  to 5.1 and provide NULL planar channels where appropriate.
+        // !!! FIXME: Since Tremor doesn't provide planar data, we just pass this through in the wrong channel order there,
+        // !!! FIXME: which is dumb but who is using these formats in the first place, let alone with low-powered Tremor?
+        #ifndef VORBIS_USE_TREMOR
+        case 3:
+            SDL_copyp(&bumped_spec, spec);
+            bumped_spec.channels = 6;
+            spec = &bumped_spec;
+            chmap = vorbis_chmap_3;
+            break;
+        case 5:
+            SDL_copyp(&bumped_spec, spec);
+            bumped_spec.channels = 6;
+            spec = &bumped_spec;
+            chmap = vorbis_chmap_5;
+            break;
+        #endif
+
+        case 6: chmap = vorbis_chmap_6; break;
+        case 7: chmap = vorbis_chmap_7; break;
+        case 8: chmap = vorbis_chmap_8; break;
+        default:
+            // no special mapping, pass through as-is.
+            break;
+    }
+
+    SDL_SetAudioStreamFormat(stream, spec, NULL);
+    SDL_SetAudioStreamInputChannelMap(stream, chmap, spec->channels);
+}
+
+
 static bool SDLCALL VORBIS_seek(void *track_userdata, Uint64 frame);
 
 static bool SDLCALL VORBIS_decode(void *track_userdata, SDL_AudioStream *stream)
@@ -277,9 +320,9 @@ static bool SDLCALL VORBIS_decode(void *track_userdata, SDL_AudioStream *stream)
         if (vi) {  // this _shouldn't_ be NULL, but if it is, we're just going on without it and hoping the stream format didn't change.
             if ((tdata->current_channels != vi->channels) || (tdata->current_freq != vi->rate)) {
                 const SDL_AudioSpec spec = { VORBIS_AUDIO_FORMAT, vi->channels, vi->rate };
-                SDL_SetAudioStreamFormat(stream, &spec, NULL);
                 tdata->current_channels = vi->channels;
                 tdata->current_freq = vi->rate;
+                UpdateVorbisStreamFormat(stream, &spec);
             }
         }
         tdata->current_bitstream = bitstream;
@@ -335,7 +378,7 @@ static bool SDLCALL VORBIS_decode(void *track_userdata, SDL_AudioStream *stream)
         #ifdef VORBIS_USE_TREMOR
         SDL_PutAudioStreamData(stream, samples, amount / framesize);
         #else
-        SDL_PutAudioStreamPlanarData(stream, (const void * const *) pcm_channels, -1, amount);
+        SDL_PutAudioStreamPlanarData(stream, (const void * const *) pcm_channels, tdata->current_channels, amount);
         #endif
         tdata->current_iteration_frames += amount;
     }