Search code examples
perlaudioliteralssquare-bracket

How can I use square brackets in perl like C?


Yesterday Computerphile uploaded a video about code golf and bitshift variations and I was really fascinated of the program generating music. This is my formatted version of the code in the video.

int g(int sample, int x, int t, int overdrive) {
 return (
  (
   3 & x & (
    sample *
    (
     (
      3 & sample >> 16
      ?
      "BY}6YB6%"
      :
      "Qj}6jQ6%"
     )[t % 8]
     +
     51
    ) >> overdrive
   )
  ) << 4
 );
}

int main(int n, int s) {
 for (int sample=0 ;; sample++)
  putchar(
   g(sample, 1, n=sample >> 14, 12)
    +
   g(sample, s=sample >> 17, n^sample >> 13, 10)
    +
   g(sample, s/3, n + ((sample >> 11) % 3), 10)
    +
   g(sample, s/5, 8 + n-((sample >> 10) % 3), 9)
  );
}

I wanted to try convert the program to pure perl and this is my attempt.

sub g {
 return (
  (
   3 & $_[1] & (
    $_[0] *
    (
     (
      3 & $_[0] >> 16
      ?
      "BY}6YB6%"
      :
      "Qj}6jQ6%"
     )[$_[2] % 8]
     +
     51
    ) >> $_[3]
   )
  ) << 4
 );
}

for($i=0;;$i++){
  print pack('C',
   g($i, 1, $n=$i >> 14, 12)
    +
   g($i, $s=$i >> 17, $n^$i >> 13, 10)
    +
   g($i, $s/3, $n + (($i >> 11) % 3), 10)
    +
   g($i, $s/5, 8 + $n-(($i >> 10) % 3), 9)
  );
}

According to the manual for putchar the the value is internally converted to an unsigned char when written. So I used the pack function in perl with C template which is an unsigned char. However when I pipe the result of both programs to aplay they don't produce the same music.

If I use inline C and call the function g from perl it does work correctly.

use Inline C => <<'END_C';

int g(int sample, int x, int t, int overdrive) {
 return (
  (
   3 & x & (
    sample *
    (
     (
      3 & sample >> 16
      ?
      "BY}6YB6%"
      :
      "Qj}6jQ6%"
     )[t % 8]
     +
     51
    ) >> overdrive
   )
  ) << 4
 );
}

END_C

for($i=0;;$i++){
  print pack('C',
   g($i, 1, $n=$i >> 14, 12)
    +
   g($i, $s=$i >> 17, $n^$i >> 13, 10)
    +
   g($i, $s/3, $n + (($i >> 11) % 3), 10)
    +
   g($i, $s/5, 8 + $n-(($i >> 10) % 3), 9)
  );
}

The only explanation I have is that [$_[2] % 8] is not doing what I think it is in perl.

How can I make the programs produce the same music in perl and C? On windows you can use perl theprogram.pl | sox -c 1 -b 8 -e unsigned -t raw -r 8k - -t waveaudio 0 if you have sox installed.


Solution

  • String is not an array in perl, you need to replace array access with call to substr() function:

    ...
    ord(substr((
     3 & $_[0] >> 16
     ?
     "BY}6YB6%"
     :
     "Qj}6jQ6%"
    ), $_[2] % 8, 1))
    ...
    

    More efficient:

    # Outside the sub.
    my $a1 = [ unpack 'C*', "BY}6YB6%" ];
    my $a2 = [ unpack 'C*', "Qj}6jQ6%" ];
    
    ...
    ${ 3 & $_[0] >> 16 ? $a1 : $a2 }[ $_[2] % 8 ]
    ...