write_iff.pl 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #!/usr/bin/perl -w
  2. use strict;
  3. require Math::BigInt;
  4. my $usage = "
  5. $0 <format> <bps> <channels> <sample-rate> <#samples> <sample-type>
  6. <format> is one of aiff,wave,wave64,rf64
  7. <bps> is 8,16,24,32
  8. <channels> is 1-8
  9. <sample-rate> is any 32-bit value
  10. <#samples> is 0-2^64-1
  11. <sample-type> is one of zero,rand
  12. ";
  13. die $usage unless @ARGV == 6;
  14. my %formats = ( 'aiff'=>1, 'wave'=>1, 'wave64'=>1, 'rf64'=>1 );
  15. my %sampletypes = ( 'zero'=>1, 'rand'=>1 );
  16. my @channelmask = ( 0, 1, 3, 7, 0x33, 0x607, 0x60f, 0, 0 ); #@@@@@@ need proper masks for 7,8
  17. my ($format, $bps, $channels, $samplerate, $samples, $sampletype) = @ARGV;
  18. my $bigsamples = new Math::BigInt $samples;
  19. die $usage unless defined $formats{$format};
  20. die $usage unless $bps == 8 || $bps == 16 || $bps == 24 || $bps == 32;
  21. die $usage unless $channels >= 1 && $channels <= 8;
  22. die $usage unless $samplerate >= 0 && $samplerate <= 4294967295;
  23. die $usage unless defined $sampletypes{$sampletype};
  24. # convert bits-per-sample to bytes-per-sample
  25. $bps /= 8;
  26. my $datasize = $samples * $bps * $channels;
  27. my $bigdatasize = $bigsamples * $bps * $channels;
  28. my $padding = int($bigdatasize & 1); # for aiff/wave/rf64 chunk alignment
  29. my $padding8 = 8 - int($bigdatasize & 7); $padding8 = 0 if $padding8 == 8; # for wave64 alignment
  30. # wave-ish file needs to be WAVEFORMATEXTENSIBLE?
  31. my $wavx = ($format eq 'wave' || $format eq 'wave64' || $format eq 'rf64') && ($channels > 2 || ($bps != 8 && $bps != 16));
  32. # write header
  33. if ($format eq 'aiff') {
  34. die "sample data too big for format\n" if 46 + $datasize + $padding > 4294967295;
  35. # header
  36. print "FORM";
  37. print pack('N', 46 + $datasize + $padding);
  38. print "AIFF";
  39. # COMM chunk
  40. print "COMM";
  41. print pack('N', 18); # chunk size = 18
  42. print pack('n', $channels);
  43. print pack('N', $samples);
  44. print pack('n', $bps * 8);
  45. print pack_sane_extended($samplerate);
  46. # SSND header
  47. print "SSND";
  48. print pack('N', $datasize + 8); # chunk size
  49. print pack('N', 0); # ssnd_offset_size
  50. print pack('N', 0); # blocksize
  51. }
  52. elsif ($format eq 'wave' || $format eq 'wave64' || $format eq 'rf64') {
  53. die "sample data too big for format\n" if $format eq 'wave' && ($wavx?60:36) + $datasize + $padding > 4294967295;
  54. # header
  55. if ($format eq 'wave') {
  56. print "RIFF";
  57. # +4 for WAVE
  58. # +8+{40,16} for fmt chunk
  59. # +8 for data chunk header
  60. print pack('V', 4 + 8+($wavx?40:16) + 8 + $datasize + $padding);
  61. print "WAVE";
  62. }
  63. elsif ($format eq 'wave64') {
  64. # RIFF GUID 66666972-912E-11CF-A5D6-28DB04C10000
  65. print "\x72\x69\x66\x66\x2E\x91\xCF\x11\xD6\xA5\x28\xDB\x04\xC1\x00\x00";
  66. # +(16+8) for RIFF GUID + size
  67. # +16 for WAVE GUID
  68. # +16+8+{40,16} for fmt chunk
  69. # +16+8 for data chunk header
  70. my $bigriffsize = $bigdatasize + (16+8) + 16 + 16+8+($wavx?40:16) + (16+8) + $padding8;
  71. print pack_64('V', $bigriffsize);
  72. # WAVE GUID 65766177-ACF3-11D3-8CD1-00C04F8EDB8A
  73. print "\x77\x61\x76\x65\xF3\xAC\xD3\x11\xD1\x8C\x00\xC0\x4F\x8E\xDB\x8A";
  74. }
  75. else {
  76. print "RF64";
  77. print pack('V', 0xffffffff);
  78. print "WAVE";
  79. # ds64 chunk
  80. print "ds64";
  81. print pack('V', 28); # chunk size
  82. # +4 for WAVE
  83. # +(8+28) for ds64 chunk
  84. # +8+{40,16} for fmt chunk
  85. # +8 for data chunk header
  86. my $bigriffsize = $bigdatasize + 4 + (8+28) + 8+($wavx?40:16) + 8 + $padding;
  87. print pack_64('V', $bigriffsize);
  88. print pack_64('V', $bigdatasize);
  89. print pack_64('V', $bigsamples);
  90. print pack('V', 0); # table size
  91. }
  92. # fmt chunk
  93. if ($format ne 'wave64') {
  94. print "fmt ";
  95. print pack('V', $wavx?40:16); # chunk size
  96. }
  97. else { # wave64
  98. # fmt GUID 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A
  99. print "\x66\x6D\x74\x20\xF3\xAC\xD3\x11\xD1\x8C\x00\xC0\x4F\x8E\xDB\x8A";
  100. print pack('V', 16+8+($wavx?40:16)); # chunk size (+16+8 for GUID and size fields)
  101. print pack('V', 0); # ...is 8 bytes for wave64
  102. }
  103. print pack('v', $wavx?65534:1); # compression code
  104. print pack('v', $channels);
  105. print pack('V', $samplerate);
  106. print pack('V', $samplerate * $channels * $bps);
  107. print pack('v', $channels * $bps); # block align = channels*((bps+7)/8)
  108. print pack('v', $bps * 8); # bits per sample = ((bps+7)/8)*8
  109. if ($wavx) {
  110. print pack('v', 22); # cbSize
  111. print pack('v', $bps * 8); # validBitsPerSample
  112. print pack('V', $channelmask[$channels]);
  113. # GUID = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}
  114. print "\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71";
  115. }
  116. # data header
  117. if ($format ne 'wave64') {
  118. print "data";
  119. print pack('V', $format eq 'wave'? $datasize : 0xffffffff);
  120. }
  121. else { # wave64
  122. # data GUID 61746164-ACF3-11D3-8CD1-00C04F8EDB8A
  123. print "\x64\x61\x74\x61\xF3\xAC\xD3\x11\xD1\x8C\x00\xC0\x4F\x8E\xDB\x8A";
  124. print pack_64('V', $bigdatasize+16+8); # +16+8 for GUID and size fields
  125. }
  126. }
  127. else {
  128. die;
  129. }
  130. # write sample data
  131. if ($sampletype eq 'zero') {
  132. my $chunk = 4096;
  133. my $buf = pack("x[".($channels*$bps*$chunk)."]");
  134. for (my $s = $samples; $s > 0; $s -= $chunk) {
  135. if ($s < $chunk) {
  136. print substr($buf, 0, $channels*$bps*$s);
  137. }
  138. else {
  139. print $buf;
  140. }
  141. }
  142. }
  143. elsif ($sampletype eq 'rand') {
  144. for (my $s = 0; $s < $samples; $s++) {
  145. for (my $c = 0; $c < $channels; $c++) {
  146. for (my $b = 0; $b < $bps; $b++) {
  147. print pack('C', int(rand(256)));
  148. }
  149. }
  150. }
  151. }
  152. else {
  153. die;
  154. }
  155. # write padding
  156. if ($format eq 'wave64') {
  157. print pack("x[$padding8]") if $padding8;
  158. }
  159. else {
  160. print "\x00" if $padding;
  161. }
  162. exit 0;
  163. sub pack_sane_extended
  164. {
  165. my $val = shift;
  166. die unless $val > 0;
  167. my $shift;
  168. for ($shift = 0; ($val>>(31-$shift)) == 0; ++$shift) {
  169. }
  170. $val <<= $shift;
  171. my $exponent = 63 - ($shift + 32);
  172. return pack('nNN', $exponent + 16383, $val, 0);
  173. }
  174. sub pack_64
  175. {
  176. my $c = shift; # 'N' for big-endian, 'V' for little-endian, ala pack()
  177. my $v1 = shift; # value, must be Math::BigInt
  178. my $v2 = $v1->copy();
  179. if ($c eq 'V') {
  180. $v1->band(0xffffffff);
  181. $v2->brsft(32);
  182. }
  183. elsif ($c eq 'N') {
  184. $v2->band(0xffffffff);
  185. $v1->brsft(32);
  186. }
  187. else {
  188. die;
  189. }
  190. return pack("$c$c", 0+$v1->bstr(), 0+$v2->bstr());
  191. }