effect_position.c
author Ryan C. Gordon <icculus@icculus.org>
Fri, 27 Dec 2002 15:13:37 +0000
changeset 201 e55fb6b45559
parent 159 01490534f9fe
child 218 a21e382190a8
permissions -rw-r--r--
(From Steven Fuller. --ryan.)

"Hi,

Here's a patch that fixes problems (attenuation on unsigned samples
would cause the samples to fall towards 0 when it should instead fall
towards 128/32768) when doing panning on 8-bit sounds and 16-bit unsigned
samples."
     1 /*
     2     SDL_mixer:  An audio mixer library based on the SDL library
     3     Copyright (C) 1997, 1998, 1999, 2000, 2001  Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Library General Public
     7     License as published by the Free Software Foundation; either
     8     version 2 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Library General Public License for more details.
    14 
    15     You should have received a copy of the GNU Library General Public
    16     License along with this library; if not, write to the Free
    17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18 
    19     This file by Ryan C. Gordon (icculus@linuxgames.com)
    20 
    21     These are some internally supported special effects that use SDL_mixer's
    22     effect callback API. They are meant for speed over quality.  :)
    23 */
    24 
    25 /* $Id$ */
    26 
    27 #include <stdio.h>
    28 #include <stdlib.h>
    29 #include <string.h>
    30 
    31 #include "SDL.h"
    32 #include "SDL_mixer.h"
    33 #include "SDL_endian.h"
    34 
    35 #define __MIX_INTERNAL_EFFECT__
    36 #include "effects_internal.h"
    37 
    38 /* profile code:
    39     #include <sys/time.h>
    40     #include <unistd.h>
    41     struct timeval tv1;
    42     struct timeval tv2;
    43     
    44     gettimeofday(&tv1, NULL);
    45 
    46         ... do your thing here ...
    47 
    48     gettimeofday(&tv2, NULL);
    49     printf("%ld\n", tv2.tv_usec - tv1.tv_usec);
    50 */
    51 
    52 
    53 /*
    54  * Positional effects...panning, distance attenuation, etc.
    55  */
    56 
    57 typedef struct _Eff_positionargs
    58 {
    59     volatile float left_f;
    60     volatile float right_f;
    61     volatile Uint8 left_u8;
    62     volatile Uint8 right_u8;
    63     volatile float distance_f;
    64     volatile Uint8 distance_u8;
    65     volatile int in_use;
    66     volatile int channels;
    67 } position_args;
    68 
    69 static position_args **pos_args_array = NULL;
    70 static position_args *pos_args_global = NULL;
    71 static int position_channels = 0;
    72 
    73 /* This just frees up the callback-specific data. */
    74 static void _Eff_PositionDone(int channel, void *udata)
    75 {
    76     if (channel < 0) {
    77         if (pos_args_global != NULL) {
    78             free(pos_args_global);
    79             pos_args_global = NULL;
    80         }
    81     }
    82 
    83     else if (pos_args_array[channel] != NULL) {
    84         free(pos_args_array[channel]);
    85         pos_args_array[channel] = NULL;
    86     }
    87 }
    88 
    89 
    90 static void _Eff_position_u8(int chan, void *stream, int len, void *udata)
    91 {
    92     volatile position_args *args = (volatile position_args *) udata;
    93     Uint8 *ptr = (Uint8 *) stream;
    94     int i;
    95 
    96         /*
    97          * if there's only a mono channnel (the only way we wouldn't have
    98          *  a len divisible by 2 here), then left_f and right_f are always
    99          *  1.0, and are therefore throwaways.
   100          */
   101     if (len % sizeof (Uint16) != 0) {
   102         *(ptr++) = (Uint8) (((float) *ptr) * args->distance_f);
   103         len--;
   104     }
   105 
   106     for (i = 0; i < len; i += sizeof (Uint8) * 2) {
   107         /* must adjust the sample so that 0 is the center */
   108         *(ptr++) = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) 
   109             * args->left_f) * args->distance_f) + 128);
   110         *(ptr++) = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) 
   111             * args->right_f) * args->distance_f) + 128);
   112     }
   113 }
   114 
   115 
   116 /*
   117  * This one runs about 10.1 times faster than the non-table version, with
   118  *  no loss in quality. It does, however, require 64k of memory for the
   119  *  lookup table. Also, this will only update position information once per
   120  *  call; the non-table version always checks the arguments for each sample,
   121  *  in case the user has called Mix_SetPanning() or whatnot again while this
   122  *  callback is running.
   123  */
   124 static void _Eff_position_table_u8(int chan, void *stream, int len, void *udata)
   125 {
   126     volatile position_args *args = (volatile position_args *) udata;
   127     Uint8 *ptr = (Uint8 *) stream;
   128     Uint32 *p;
   129     int i;
   130     Uint8 *l = ((Uint8 *) _Eff_volume_table) + (256 * args->left_u8);
   131     Uint8 *r = ((Uint8 *) _Eff_volume_table) + (256 * args->right_u8);
   132     Uint8 *d = ((Uint8 *) _Eff_volume_table) + (256 * args->distance_u8);
   133 
   134         /*
   135          * if there's only a mono channnel, then l[] and r[] are always
   136          *  volume 255, and are therefore throwaways. Still, we have to
   137          *  be sure not to overrun the audio buffer...
   138          */
   139     while (len % sizeof (Uint32) != 0) {
   140         *(ptr++) = d[l[*ptr]];
   141         if (args->channels == 2)
   142             *(ptr++) = d[r[*ptr]];
   143         len -= args->channels;
   144     }
   145 
   146     p = (Uint32 *) ptr;
   147 
   148     for (i = 0; i < len; i += sizeof (Uint32)) {
   149 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
   150         *(p++) = (d[l[(*p & 0xFF000000) >> 24]] << 24) |
   151                  (d[r[(*p & 0x00FF0000) >> 16]] << 16) |
   152                  (d[l[(*p & 0x0000FF00) >>  8]] <<  8) |
   153                  (d[r[(*p & 0x000000FF)      ]]      ) ;
   154 #else
   155         *(p++) = (d[r[(*p & 0xFF000000) >> 24]] << 24) |
   156                  (d[l[(*p & 0x00FF0000) >> 16]] << 16) |
   157                  (d[r[(*p & 0x0000FF00) >>  8]] <<  8) |
   158                  (d[l[(*p & 0x000000FF)      ]]      ) ;
   159 #endif
   160     }
   161 }
   162 
   163 
   164 static void _Eff_position_s8(int chan, void *stream, int len, void *udata)
   165 {
   166     volatile position_args *args = (volatile position_args *) udata;
   167     Sint8 *ptr = (Sint8 *) stream;
   168     int i;
   169 
   170         /*
   171          * if there's only a mono channnel (the only way we wouldn't have
   172          *  a len divisible by 2 here), then left_f and right_f are always
   173          *  1.0, and are therefore throwaways.
   174          */
   175     if (len % sizeof (Sint16) != 0) {
   176         *(ptr++) = (Sint8) (((float) *ptr) * args->distance_f);
   177         len--;
   178     }
   179 
   180     for (i = 0; i < len; i += sizeof (Sint8) * 2) {
   181         *(ptr++) = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f);
   182         *(ptr++) = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f);
   183     }
   184 }
   185 
   186 
   187 /*
   188  * This one runs about 10.1 times faster than the non-table version, with
   189  *  no loss in quality. It does, however, require 64k of memory for the
   190  *  lookup table. Also, this will only update position information once per
   191  *  call; the non-table version always checks the arguments for each sample,
   192  *  in case the user has called Mix_SetPanning() or whatnot again while this
   193  *  callback is running.
   194  */
   195 static void _Eff_position_table_s8(int chan, void *stream, int len, void *udata)
   196 {
   197     volatile position_args *args = (volatile position_args *) udata;
   198     Sint8 *ptr = (Sint8 *) stream;
   199     Uint32 *p;
   200     int i;
   201     Sint8 *l = ((Sint8 *) _Eff_volume_table) + (256 * args->left_u8);
   202     Sint8 *r = ((Sint8 *) _Eff_volume_table) + (256 * args->right_u8);
   203     Sint8 *d = ((Sint8 *) _Eff_volume_table) + (256 * args->distance_u8);
   204 
   205 
   206     while (len % sizeof (Uint32) != 0) {
   207         *(ptr++) = d[l[*ptr]];
   208         if (args->channels > 1)
   209             *(ptr++) = d[r[*ptr]];
   210         len -= args->channels;
   211     }
   212 
   213     p = (Uint32 *) ptr;
   214 
   215     for (i = 0; i < len; i += sizeof (Uint32)) {
   216 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
   217         *(p++) = (d[l[((Sint16)(Sint8)((*p & 0xFF000000) >> 24))+128]] << 24) |
   218                  (d[r[((Sint16)(Sint8)((*p & 0x00FF0000) >> 16))+128]] << 16) |
   219                  (d[l[((Sint16)(Sint8)((*p & 0x0000FF00) >>  8))+128]] <<  8) |
   220                  (d[r[((Sint16)(Sint8)((*p & 0x000000FF)      ))+128]]      ) ;
   221 #else
   222         *(p++) = (d[r[((Sint16)(Sint8)((*p & 0xFF000000) >> 24))+128]] << 24) |
   223                  (d[l[((Sint16)(Sint8)((*p & 0x00FF0000) >> 16))+128]] << 16) |
   224                  (d[r[((Sint16)(Sint8)((*p & 0x0000FF00) >>  8))+128]] <<  8) |
   225                  (d[l[((Sint16)(Sint8)((*p & 0x000000FF)      ))+128]]      ) ;
   226 #endif
   227     }
   228 
   229 
   230 }
   231 
   232 
   233 /* !!! FIXME : Optimize the code for 16-bit samples? */
   234 
   235 static void _Eff_position_u16lsb(int chan, void *stream, int len, void *udata)
   236 {
   237     volatile position_args *args = (volatile position_args *) udata;
   238     Uint16 *ptr = (Uint16 *) stream;
   239     int i;
   240 
   241     for (i = 0; i < len; i += sizeof (Uint16) * 2) {
   242 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
   243         Sint16 sampl = (Sint16) (SDL_Swap16(*(ptr+0)) - 32768);
   244         Sint16 sampr = (Sint16) (SDL_Swap16(*(ptr+1)) - 32768);
   245         
   246         Uint16 swapl = (Uint16) ((Sint16) (((float) sampl * args->left_f)
   247                                     * args->distance_f) + 32768);
   248         Uint16 swapr = (Uint16) ((Sint16) (((float) sampr * args->right_f)
   249                                     * args->distance_f) + 32768);
   250 
   251         *(ptr++) = (Uint16) SDL_Swap16(swapl);
   252         *(ptr++) = (Uint16) SDL_Swap16(swapr);
   253 #else
   254         Sint16 sampl = (Sint16) (*(ptr+0) - 32768);
   255         Sint16 sampr = (Sint16) (*(ptr+1) - 32768);
   256 
   257         *(ptr++) = (Uint16) ((Sint16) (((float) sampl * args->left_f) 
   258                                     * args->distance_f) + 32768);
   259         *(ptr++) = (Uint16) ((Sint16) (((float) sampr * args->right_f) 
   260                                     * args->distance_f) + 32768);
   261 #endif
   262     }
   263 }
   264 
   265 static void _Eff_position_s16lsb(int chan, void *stream, int len, void *udata)
   266 {
   267     /* 16 signed bits (lsb) * 2 channels. */
   268     volatile position_args *args = (volatile position_args *) udata;
   269     Sint16 *ptr = (Sint16 *) stream;
   270     int i;
   271 
   272     for (i = 0; i < len; i += sizeof (Sint16) * 2) {
   273 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
   274         Sint16 swapl = (Sint16) ((((float) SDL_Swap16(*(ptr))) *
   275                                     args->left_f) * args->distance_f);
   276         Sint16 swapr = (Sint16) ((((float) SDL_Swap16(*(ptr+1))) *
   277                                     args->right_f) * args->distance_f);
   278         *(ptr++) = (Sint16) SDL_Swap16(swapl);
   279         *(ptr++) = (Sint16) SDL_Swap16(swapr);
   280 #else
   281         *(ptr++) = (Sint16) ((((float) *ptr)*args->left_f)*args->distance_f);
   282         *(ptr++) = (Sint16) ((((float) *ptr)*args->right_f)*args->distance_f);
   283 #endif
   284     }
   285 }
   286 
   287 static void _Eff_position_u16msb(int chan, void *stream, int len, void *udata)
   288 {
   289     /* 16 signed bits (lsb) * 2 channels. */
   290     volatile position_args *args = (volatile position_args *) udata;
   291     Uint16 *ptr = (Uint16 *) stream;
   292     int i;
   293 
   294     for (i = 0; i < len; i += sizeof (Sint16) * 2) {
   295 #if (SDL_BYTEORDER == SDL_LIL_ENDIAN)
   296         Sint16 sampl = (Sint16) (SDL_Swap16(*(ptr+0)) - 32768);
   297         Sint16 sampr = (Sint16) (SDL_Swap16(*(ptr+1)) - 32768);
   298         
   299         Uint16 swapl = (Uint16) ((Sint16) (((float) sampl * args->left_f)
   300                                     * args->distance_f) + 32768);
   301         Uint16 swapr = (Uint16) ((Sint16) (((float) sampr * args->right_f)
   302                                     * args->distance_f) + 32768);
   303 
   304         *(ptr++) = (Uint16) SDL_Swap16(swapl);
   305         *(ptr++) = (Uint16) SDL_Swap16(swapr);
   306 #else
   307         Sint16 sampl = (Sint16) (*(ptr+0) - 32768);
   308         Sint16 sampr = (Sint16) (*(ptr+1) - 32768);
   309 
   310         *(ptr++) = (Uint16) ((Sint16) (((float) sampl * args->left_f) 
   311                                     * args->distance_f) + 32768);
   312         *(ptr++) = (Uint16) ((Sint16) (((float) sampr * args->right_f) 
   313                                     * args->distance_f) + 32768);
   314 #endif
   315     }
   316 }
   317 
   318 static void _Eff_position_s16msb(int chan, void *stream, int len, void *udata)
   319 {
   320     /* 16 signed bits (lsb) * 2 channels. */
   321     volatile position_args *args = (volatile position_args *) udata;
   322     Sint16 *ptr = (Sint16 *) stream;
   323     int i;
   324 
   325     for (i = 0; i < len; i += sizeof (Sint16) * 2) {
   326 #if (SDL_BYTEORDER == SDL_LIL_ENDIAN)
   327         Sint16 swapl = (Sint16) ((((float) SDL_Swap16(*(ptr))) *
   328                                     args->left_f) * args->distance_f);
   329         Sint16 swapr = (Sint16) ((((float) SDL_Swap16(*(ptr+1))) *
   330                                     args->right_f) * args->distance_f);
   331         *(ptr++) = (Sint16) SDL_Swap16(swapl);
   332         *(ptr++) = (Sint16) SDL_Swap16(swapr);
   333 #else
   334         *(ptr++) = (Sint16) ((((float) *ptr)*args->left_f)*args->distance_f);
   335         *(ptr++) = (Sint16) ((((float) *ptr)*args->right_f)*args->distance_f);
   336 #endif
   337     }
   338 }
   339 
   340 
   341 static void init_position_args(position_args *args)
   342 {
   343     memset(args, '\0', sizeof (position_args));
   344     args->in_use = 0;
   345     args->left_u8 = args->right_u8 = args->distance_u8 = 255;
   346     args->left_f  = args->right_f  = args->distance_f  = 1.0f;
   347     Mix_QuerySpec(NULL, NULL, (int *) &args->channels);
   348 }
   349 
   350 
   351 static position_args *get_position_arg(int channel)
   352 {
   353     void *rc;
   354     int i;
   355 
   356     if (channel < 0) {
   357         if (pos_args_global == NULL) {
   358             pos_args_global = malloc(sizeof (position_args));
   359             if (pos_args_global == NULL) {
   360                 Mix_SetError("Out of memory");
   361                 return(NULL);
   362             }
   363             init_position_args(pos_args_global);
   364         }
   365 
   366         return(pos_args_global);
   367     }
   368 
   369     if (channel >= position_channels) {
   370         rc = realloc(pos_args_array, (channel + 1) * sizeof (position_args *));
   371         if (rc == NULL) {
   372             Mix_SetError("Out of memory");
   373             return(NULL);
   374         }
   375         pos_args_array = (position_args **) rc;
   376         for (i = position_channels; i <= channel; i++) {
   377             pos_args_array[i] = NULL;
   378         }
   379         position_channels = channel + 1;
   380     }
   381 
   382     if (pos_args_array[channel] == NULL) {
   383         pos_args_array[channel] = (position_args *)malloc(sizeof(position_args));
   384         if (pos_args_array[channel] == NULL) {
   385             Mix_SetError("Out of memory");
   386             return(NULL);
   387         }
   388         init_position_args(pos_args_array[channel]);
   389     }
   390 
   391     return(pos_args_array[channel]);
   392 }
   393 
   394 
   395 static Mix_EffectFunc_t get_position_effect_func(Uint16 format)
   396 {
   397     Mix_EffectFunc_t f = NULL;
   398 
   399     switch (format) {
   400         case AUDIO_U8:
   401             f = (_Eff_build_volume_table_u8()) ? _Eff_position_table_u8 :
   402                                                  _Eff_position_u8;
   403             break;
   404 
   405         case AUDIO_S8:
   406             f = (_Eff_build_volume_table_s8()) ? _Eff_position_table_s8 :
   407                                                  _Eff_position_s8;
   408             break;
   409 
   410         case AUDIO_U16LSB:
   411             f = _Eff_position_u16lsb;
   412             break;
   413 
   414         case AUDIO_S16LSB:
   415             f = _Eff_position_s16lsb;
   416             break;
   417 
   418         case AUDIO_U16MSB:
   419             f = _Eff_position_u16msb;
   420             break;
   421 
   422         case AUDIO_S16MSB:
   423             f = _Eff_position_s16msb;
   424             break;
   425 
   426         default:
   427             Mix_SetError("Unsupported audio format");
   428     }
   429 
   430     return(f);
   431 }
   432 
   433 
   434 int Mix_SetPanning(int channel, Uint8 left, Uint8 right)
   435 {
   436     Mix_EffectFunc_t f = NULL;
   437     int channels;
   438     Uint16 format;
   439     position_args *args = NULL;
   440 
   441     Mix_QuerySpec(NULL, &format, &channels);
   442 
   443     if (channels != 2)    /* it's a no-op; we call that successful. */
   444         return(1);
   445 
   446     f = get_position_effect_func(format);
   447     if (f == NULL)
   448         return(0);
   449 
   450     args = get_position_arg(channel);
   451     if (!args)
   452         return(0);
   453 
   454         /* it's a no-op; unregister the effect, if it's registered. */
   455     if ((args->distance_u8 == 255) && (left == 255) &&
   456         (right == 255) && (args->in_use))
   457     {
   458         return(Mix_UnregisterEffect(channel, f));
   459     }
   460 
   461     args->left_u8 = left;
   462     args->right_u8 = right;
   463     args->left_f = ((float) left) / 255.0f;
   464     args->right_f = ((float) right) / 255.0f;
   465     if (!args->in_use) {
   466         args->in_use = 1;
   467         return(Mix_RegisterEffect(channel, f, _Eff_PositionDone, (void *) args));
   468     }
   469 
   470     return(1);
   471 }
   472 
   473 
   474 int Mix_SetDistance(int channel, Uint8 distance)
   475 {
   476     Mix_EffectFunc_t f = NULL;
   477     Uint16 format;
   478     position_args *args = NULL;
   479 
   480     Mix_QuerySpec(NULL, &format, NULL);
   481     f = get_position_effect_func(format);
   482     if (f == NULL)
   483         return(0);
   484 
   485     args = get_position_arg(channel);
   486     if (!args)
   487         return(0);
   488 
   489     distance = 255 - distance;  /* flip it to our scale. */
   490 
   491         /* it's a no-op; unregister the effect, if it's registered. */
   492     if ((distance == 255) && (args->left_u8 == 255) &&
   493         (args->right_u8 == 255) && (args->in_use))
   494     {
   495         return(Mix_UnregisterEffect(channel, f));
   496     }
   497 
   498     args->distance_u8 = distance;
   499     args->distance_f = ((float) distance) / 255.0f;
   500     if (!args->in_use) {
   501         args->in_use = 1;
   502         return(Mix_RegisterEffect(channel, f, _Eff_PositionDone, (void *) args));
   503     }
   504 
   505     return(1);
   506 }
   507 
   508 
   509 int Mix_SetPosition(int channel, Sint16 angle, Uint8 distance)
   510 {
   511     Mix_EffectFunc_t f = NULL;
   512     Uint16 format;
   513     int channels;
   514     position_args *args = NULL;
   515     Uint8 left = 255, right = 255;
   516 
   517     Mix_QuerySpec(NULL, &format, &channels);
   518     f = get_position_effect_func(format);
   519     if (f == NULL)
   520         return(0);
   521 
   522         /* unwind the angle...it'll be between 0 and 359. */
   523     while (angle >= 360) angle -= 360;
   524     while (angle < 0) angle += 360;
   525 
   526     args = get_position_arg(channel);
   527     if (!args)
   528         return(0);
   529 
   530         /* it's a no-op; unregister the effect, if it's registered. */
   531     if ((!distance) && (!angle) && (args->in_use))
   532         return(Mix_UnregisterEffect(channel, f));
   533 
   534     if (channels == 2)
   535     {
   536         /*
   537          * We only attenuate by position if the angle falls on the far side
   538          *  of center; That is, an angle that's due north would not attenuate
   539          *  either channel. Due west attenuates the right channel to 0.0, and
   540          *  due east attenuates the left channel to 0.0. Slightly east of
   541          *  center attenuates the left channel a little, and the right channel
   542          *  not at all. I think of this as occlusion by one's own head.  :)
   543          *
   544          *   ...so, we split our angle circle into four quadrants...
   545          */
   546         if (angle < 90) {
   547             left = 255 - ((Uint8) (255.0f * (((float) angle) / 89.0f)));
   548         } else if (angle < 180) {
   549             left = (Uint8) (255.0f * (((float) (angle - 90)) / 89.0f));
   550         } else if (angle < 270) {
   551             right = 255 - ((Uint8) (255.0f * (((float) (angle - 180)) / 89.0f)));
   552         } else {
   553             right = (Uint8) (255.0f * (((float) (angle - 270)) / 89.0f));
   554         }
   555     }
   556 
   557     distance = 255 - distance;  /* flip it to scale Mix_SetDistance() uses. */
   558 
   559     args->left_u8 = left;
   560     args->left_f = ((float) left) / 255.0f;
   561     args->right_u8 = right;
   562     args->right_f = ((float) right) / 255.0f;
   563     args->distance_u8 = distance;
   564     args->distance_f = ((float) distance) / 255.0f;
   565     if (!args->in_use) {
   566         args->in_use = 1;
   567         return(Mix_RegisterEffect(channel, f, _Eff_PositionDone, (void *) args));
   568     }
   569 
   570     return(1);
   571 }
   572 
   573 
   574 /* end of effects_position.c ... */
   575