First shot at autogenerated audio resamplers.
authorRyan C. Gordon <icculus@icculus.org>
Fri, 09 Jan 2009 15:41:45 +0000
changeset 3008 786a48f8309c
parent 3007 b30409e106f2
child 3009 546c022a9ae5
First shot at autogenerated audio resamplers. Don't check in a new SDL_audiotypecvt.c yet, though.
src/audio/sdlgenaudiocvt.pl
--- a/src/audio/sdlgenaudiocvt.pl	Fri Jan 09 13:58:28 2009 +0000
+++ b/src/audio/sdlgenaudiocvt.pl	Fri Jan 09 15:41:45 2009 +0000
@@ -16,9 +16,21 @@
     F32MSB
 );
 
+my @channels = ( 1, 2, 4, 6, 8 );
 my %funcs;
+my $custom_converters = 0;
+
 
-my $custom_converters = 0;
+sub getTypeConvertHashId {
+    my ($from, $to) = @_;
+    return "TYPECONVERTER $from/$to";
+}
+
+
+sub getResamplerHashId {
+    my ($from, $channels, $upsample, $multiple) = @_;
+    return "RESAMPLER $from/$channels/$upsample/$multiple";
+}
 
 
 sub outputHeader {
@@ -66,6 +78,8 @@
 
 sub outputFooter {
     print <<EOF;
+/* $custom_converters converters generated. */
+
 /* *INDENT-ON* */
 
 /* vi: set ts=4 sw=4 expandtab: */
@@ -162,7 +176,7 @@
 
     return if ($diffs == 0);
 
-    my $hashid = "$from/$to";
+    my $hashid = getTypeConvertHashId($from, $to);
     if (1) { # !!! FIXME: if ($diffs > 1) {
         my $sym = "SDL_Convert_${from}_to_${to}";
         $funcs{$hashid} = $sym;
@@ -274,39 +288,407 @@
     }
 }
 
-outputHeader();
+
+sub buildTypeConverters {
+    foreach (@audiotypes) {
+        my $from = $_;
+        foreach (@audiotypes) {
+            my $to = $_;
+            buildCvtFunc($from, $to);
+        }
+    }
+
+    print "const SDL_AudioTypeFilters sdl_audio_type_filters[] =\n{\n";
+    foreach (@audiotypes) {
+        my $from = $_;
+        foreach (@audiotypes) {
+            my $to = $_;
+            if ($from ne $to) {
+                my $hashid = getTypeConvertHashId($from, $to);
+                my $sym = $funcs{$hashid};
+                print("    { AUDIO_$from, AUDIO_$to, $sym },\n");
+            }
+        }
+    }
+
+    print "};\n\n\n";
+}
+
+sub getBiggerCtype {
+    my ($isfloat, $size) = @_;
+
+    if ($isfloat) {
+        if ($size == 32) {
+            return 'double';
+        }
+        die("bug in script.\n");
+    }
+
+    if ($size == 8) {
+        return 'Sint16';
+    } elsif ($size == 16) {
+        return 'Sint32'
+    } elsif ($size == 32) {
+        return 'Sint64'
+    }
+
+    die("bug in script.\n");
+}
+
+
+# These handle arbitrary resamples...44100Hz to 48000Hz, for example.
+# Man, this code is skanky.
+sub buildArbitraryResampleFunc {
+    # !!! FIXME: we do a lot of unnecessary and ugly casting in here, due to getSwapFunc().
+    my ($from, $channels, $upsample) = @_;
+    my ($fsigned, $ffloat, $fsize, $fendian, $fctype) = splittype($from);
+
+    my $bigger = getBiggerCtype($ffloat, $fsize);
+    my $interp = ($ffloat) ? '* 0.5' : '>> 1';
+
+    my $resample = ($upsample) ? 'Upsample' : 'Downsample';
+    my $hashid = getResamplerHashId($from, $channels, $upsample, 0);
+    my $sym = "SDL_${resample}_${from}_${channels}c";
+    $funcs{$hashid} = $sym;
+    $custom_converters++;
+
+    my $fudge = $fsize * $channels * 2;  # !!! FIXME
+    my $eps_adjust = ($upsample) ? 'dstsize' : 'srcsize';
+    my $incr = '';
+    my $incr2 = '';
+
+
+    # !!! FIXME: DEBUG_CONVERT should report frequencies.
+    print <<EOF;
+static void SDLCALL
+${sym}(SDL_AudioCVT * cvt, SDL_AudioFormat format)
+{
+#ifdef DEBUG_CONVERT
+    fprintf(stderr, "$resample arbitrary (x%f) AUDIO_${from}, ${channels} channels.\\n", cvt->rate_incr);
+#endif
+
+    const int srcsize = cvt->len_cvt - $fudge;
+    const int dstsize = (int) (((double)cvt->len_cvt) * cvt->rate_incr);
+    register int eps = 0;
+EOF
 
-foreach (@audiotypes) {
-    my $from = $_;
-    foreach (@audiotypes) {
-        my $to = $_;
-        buildCvtFunc($from, $to);
+    # Upsampling (growing the buffer) needs to work backwards, since we
+    #  overwrite the buffer as we go.
+    if ($upsample) {
+        print <<EOF;
+    $fctype *dst = (($fctype *) (cvt->buf + dstsize)) - $channels;
+    const $fctype *src = (($fctype *) (cvt->buf + cvt->len_cvt)) - $channels;
+    const $fctype *target = ((const $fctype *) cvt->buf) - $channels;
+EOF
+    } else {
+        print <<EOF;
+    $fctype *dst = ($fctype *) cvt->buf;
+    const $fctype *src = ($fctype *) cvt->buf;
+    const $fctype *target = (const $fctype *) (cvt->buf + dstsize);
+EOF
+    }
+
+    for (my $i = 0; $i < $channels; $i++) {
+        my $idx = ($upsample) ? (($channels - $i) - 1) : $i;
+        my $val = getSwapFunc($fsize, $fsigned, $ffloat, $fendian, "src[$idx]");
+        print <<EOF;
+    $fctype sample${idx} = $val;
+EOF
+    }
+
+    for (my $i = 0; $i < $channels; $i++) {
+        my $idx = ($upsample) ? (($channels - $i) - 1) : $i;
+        print <<EOF;
+    $fctype last_sample${idx} = sample${idx};
+EOF
+    }
+
+    print <<EOF;
+    while (dst != target) {
+EOF
+
+    if ($upsample) {
+        for (my $i = 0; $i < $channels; $i++) {
+            # !!! FIXME: don't do this swap every write, just when the samples change.
+            my $idx = (($channels - $i) - 1);
+            my $val = getSwapFunc($fsize, $fsigned, $ffloat, $fendian, "sample${idx}");
+            print <<EOF;
+        dst[$idx] = $val;
+EOF
+        }
+
+        $incr = ($channels == 1) ? 'dst--' : "dst -= $channels";
+        $incr2 = ($channels == 1) ? 'src--' : "src -= $channels";
+
+        print <<EOF;
+        $incr;
+        eps += srcsize;
+        if ((eps << 1) >= dstsize) {
+            $incr2;
+EOF
+    } else {  # downsample.
+        $incr = ($channels == 1) ? 'src++' : "src += $channels";
+        print <<EOF;
+        $incr;
+        eps += dstsize;
+        if ((eps << 1) >= srcsize) {
+EOF
+        for (my $i = 0; $i < $channels; $i++) {
+            my $val = getSwapFunc($fsize, $fsigned, $ffloat, $fendian, "sample${i}");
+            print <<EOF;
+            dst[$i] = $val;
+EOF
+        }
+
+        $incr = ($channels == 1) ? 'dst++' : "dst += $channels";
+        print <<EOF;
+            $incr;
+EOF
+    }
+
+    for (my $i = 0; $i < $channels; $i++) {
+        my $idx = ($upsample) ? (($channels - $i) - 1) : $i;
+        my $swapped = getSwapFunc($fsize, $fsigned, $ffloat, $fendian, "src[$idx]");
+        print <<EOF;
+            sample${idx} = ($fctype) (((($bigger) $swapped) + (($bigger) last_sample${idx})) $interp);
+EOF
+    }
+
+    for (my $i = 0; $i < $channels; $i++) {
+        my $idx = ($upsample) ? (($channels - $i) - 1) : $i;
+        print <<EOF;
+            last_sample${idx} = sample${idx};
+EOF
+    }
+
+    print <<EOF;
+            eps -= $eps_adjust;
+        }
+    }
+EOF
+
+        print <<EOF;
+    cvt->len_cvt = dstsize;
+    if (cvt->filters[++cvt->filter_index]) {
+        cvt->filters[cvt->filter_index] (cvt, format);
     }
 }
 
-print <<EOF;
-const SDL_AudioTypeFilters sdl_audio_type_filters[] =
+EOF
+
+}
+
+# These handle clean resamples...doubling and quadrupling the sample rate, etc.
+sub buildMultipleResampleFunc {
+    # !!! FIXME: we do a lot of unnecessary and ugly casting in here, due to getSwapFunc().
+    my ($from, $channels, $upsample, $multiple) = @_;
+    my ($fsigned, $ffloat, $fsize, $fendian, $fctype) = splittype($from);
+
+    my $bigger = getBiggerCtype($ffloat, $fsize);
+    my $interp = ($ffloat) ? '* 0.5' : '>> 1';
+    my $interp2 = ($ffloat) ? '* 0.25' : '>> 2';
+    my $mult3 = ($ffloat) ? '3.0' : '3';
+    my $lencvtop = ($upsample) ? '*' : '/';
+
+    my $resample = ($upsample) ? 'Upsample' : 'Downsample';
+    my $hashid = getResamplerHashId($from, $channels, $upsample, $multiple);
+    my $sym = "SDL_${resample}_${from}_${channels}c_x${multiple}";
+    $funcs{$hashid} = $sym;
+    $custom_converters++;
+
+    # !!! FIXME: DEBUG_CONVERT should report frequencies.
+    print <<EOF;
+static void SDLCALL
+${sym}(SDL_AudioCVT * cvt, SDL_AudioFormat format)
 {
+#ifdef DEBUG_CONVERT
+    fprintf(stderr, "$resample (x${multiple}) AUDIO_${from}, ${channels} channels.\\n");
+#endif
+
+    const int srcsize = cvt->len_cvt;
+    const int dstsize = cvt->len_cvt $lencvtop $multiple;
+EOF
+
+    # Upsampling (growing the buffer) needs to work backwards, since we
+    #  overwrite the buffer as we go.
+    if ($upsample) {
+        print <<EOF;
+    $fctype *dst = (($fctype *) (cvt->buf + dstsize)) - $channels;
+    const $fctype *src = (($fctype *) (cvt->buf + cvt->len_cvt)) - $channels;
+    const $fctype *target = ((const $fctype *) cvt->buf) - $channels;
+EOF
+    } else {
+        print <<EOF;
+    $fctype *dst = ($fctype *) cvt->buf;
+    const $fctype *src = ($fctype *) cvt->buf;
+    const $fctype *target = (const $fctype *) (cvt->buf + dstsize);
+EOF
+    }
+
+    for (my $i = 0; $i < $channels; $i++) {
+        my $idx = ($upsample) ? (($channels - $i) - 1) : $i;
+        my $val = getSwapFunc($fsize, $fsigned, $ffloat, $fendian, "src[$idx]");
+        print <<EOF;
+    $bigger last_sample${idx} = ($bigger) $val;
+EOF
+    }
+
+    print <<EOF;
+    while (dst != target) {
+EOF
+
+    for (my $i = 0; $i < $channels; $i++) {
+        my $idx = ($upsample) ? (($channels - $i) - 1) : $i;
+        my $val = getSwapFunc($fsize, $fsigned, $ffloat, $fendian, "src[$idx]");
+        print <<EOF;
+        const $bigger sample${idx} = ($bigger) $val;
+EOF
+    }
+
+    my $incr = '';
+    if ($upsample) {
+        $incr = ($channels == 1) ? 'src--' : "src -= $channels";
+    } else {
+        my $amount = $channels * $multiple;
+        $incr = "src += $amount";  # can't ever be 1, so no "++" version.
+    }
+
+
+    print <<EOF;
+        $incr;
 EOF
 
-foreach (@audiotypes) {
-    my $from = $_;
-    foreach (@audiotypes) {
-        my $to = $_;
-        if ($from ne $to) {
-            my $hashid = "$from/$to";
-            my $sym = $funcs{$hashid};
-            print("    { AUDIO_$from, AUDIO_$to, $sym },\n");
+    # !!! FIXME: This really begs for some Altivec or SSE, etc.
+    if ($upsample) {
+        if ($multiple == 2) {
+            for (my $i = $channels-1; $i >= 0; $i--) {
+                my $dsti = $i + $channels;
+                print <<EOF;
+        dst[$dsti] = ($fctype) ((sample${i} + last_sample${i}) $interp);
+EOF
+            }
+            for (my $i = $channels-1; $i >= 0; $i--) {
+                my $dsti = $i;
+                print <<EOF;
+        dst[$dsti] = ($fctype) sample${i};
+EOF
+            }
+        } elsif ($multiple == 4) {
+            for (my $i = $channels-1; $i >= 0; $i--) {
+                my $dsti = $i + ($channels * 3);
+                print <<EOF;
+        dst[$dsti] = ($fctype) sample${i};
+EOF
+            }
+
+            for (my $i = $channels-1; $i >= 0; $i--) {
+                my $dsti = $i + ($channels * 2);
+                print <<EOF;
+        dst[$dsti] = ($fctype) ((($mult3 * sample${i}) + last_sample${i}) $interp2);
+EOF
+            }
+
+            for (my $i = $channels-1; $i >= 0; $i--) {
+                my $dsti = $i + ($channels * 1);
+                print <<EOF;
+        dst[$dsti] = ($fctype) ((sample${i} + last_sample${i}) $interp);
+EOF
+            }
+
+            for (my $i = $channels-1; $i >= 0; $i--) {
+                my $dsti = $i + ($channels * 0);
+                print <<EOF;
+        dst[$dsti] = ($fctype) ((sample${i} + ($mult3 * last_sample${i})) $interp2);
+EOF
+            }
+        } else {
+            die('bug in program.');  # we only handle x2 and x4.
         }
+    } else {  # downsample.
+        if ($multiple == 2) {
+            for (my $i = 0; $i < $channels; $i++) {
+                print <<EOF;
+        dst[$i] = ($fctype) ((sample${i} + last_sample${i}) $interp);
+EOF
+            }
+        } elsif ($multiple == 4) {
+            # !!! FIXME: interpolate all 4 samples?
+            for (my $i = 0; $i < $channels; $i++) {
+                print <<EOF;
+        dst[$i] = ($fctype) ((sample${i} + last_sample${i}) $interp);
+EOF
+            }
+        } else {
+            die('bug in program.');  # we only handle x2 and x4.
+        }
+    }
+
+    for (my $i = 0; $i < $channels; $i++) {
+        my $idx = ($upsample) ? (($channels - $i) - 1) : $i;
+        print <<EOF;
+        last_sample${idx} = sample${idx};
+EOF
+    }
+
+    if ($upsample) {
+        my $amount = $channels * $multiple;
+        $incr = "dst -= $amount";  # can't ever be 1, so no "--" version.
+    } else {
+        $incr = ($channels == 1) ? 'dst++' : "dst += $channels";
+    }
+
+    print <<EOF;
+        $incr;
+    }
+
+    cvt->len_cvt = dstsize;
+    if (cvt->filters[++cvt->filter_index]) {
+        cvt->filters[cvt->filter_index] (cvt, format);
     }
 }
 
-print <<EOF;
-};
+EOF
+
+}
+
+sub buildResamplers {
+    foreach (@audiotypes) {
+        my $from = $_;
+        foreach (@channels) {
+            my $channel = $_;
+            for (my $multiple = 2; $multiple <= 4; $multiple += 2) {
+                buildMultipleResampleFunc($from, $channel, 1, $multiple);
+                buildMultipleResampleFunc($from, $channel, 0, $multiple);
+            }
+            buildArbitraryResampleFunc($from, $channel, 1);
+            buildArbitraryResampleFunc($from, $channel, 0);
+        }
+    }
+
+    print "const SDL_AudioRateFilters sdl_audio_rate_filters[] =\n{\n";
+    foreach (@audiotypes) {
+        my $from = $_;
+        foreach (@channels) {
+            my $channel = $_;
+            for (my $multiple = 0; $multiple <= 4; $multiple += 2) {
+                for (my $upsample = 0; $upsample <= 1; $upsample++) {
+                    my $hashid = getResamplerHashId($from, $channel, $upsample, $multiple);
+                    my $sym = $funcs{$hashid};
+                    print("    { AUDIO_$from, $channel, $upsample, $multiple, $sym },\n");
+                }
+            }
+        }
+    }
+
+    print "};\n\n";
+}
 
 
-EOF
+# mainline ...
 
+outputHeader();
+buildTypeConverters();
+buildResamplers();
 outputFooter();
 
 exit 0;