wavefile 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -23
  3. data/README.markdown +27 -43
  4. data/lib/wavefile.rb +4 -1
  5. data/lib/wavefile/buffer.rb +7 -5
  6. data/lib/wavefile/chunk_readers.rb +1 -2
  7. data/lib/wavefile/chunk_readers/base_chunk_reader.rb +13 -0
  8. data/lib/wavefile/chunk_readers/data_chunk_reader.rb +12 -3
  9. data/lib/wavefile/chunk_readers/format_chunk_reader.rb +2 -10
  10. data/lib/wavefile/chunk_readers/riff_reader.rb +73 -26
  11. data/lib/wavefile/chunk_readers/sample_chunk_reader.rb +74 -0
  12. data/lib/wavefile/format.rb +13 -9
  13. data/lib/wavefile/reader.rb +15 -7
  14. data/lib/wavefile/sampler_info.rb +162 -0
  15. data/lib/wavefile/sampler_loop.rb +142 -0
  16. data/lib/wavefile/smpte_timecode.rb +61 -0
  17. data/lib/wavefile/writer.rb +7 -1
  18. data/test/fixtures/wave/invalid/data_chunk_ends_after_chunk_id.wav +0 -0
  19. data/test/fixtures/wave/invalid/data_chunk_has_incomplete_chunk_size.wav +0 -0
  20. data/test/fixtures/wave/invalid/data_chunk_truncated.wav +0 -0
  21. data/test/fixtures/wave/invalid/format_chunk_after_data_chunk.wav +0 -0
  22. data/test/fixtures/wave/invalid/incomplete_riff_format.wav +0 -0
  23. data/test/fixtures/wave/invalid/incomplete_riff_header.wav +1 -1
  24. data/test/fixtures/wave/invalid/no_format_chunk_size.wav +0 -0
  25. data/test/fixtures/wave/invalid/no_riff_format.wav +0 -0
  26. data/test/fixtures/wave/invalid/riff_chunk_has_incomplete_chunk_size.wav +1 -0
  27. data/test/fixtures/wave/invalid/smpl_chunk_empty.wav +0 -0
  28. data/test/fixtures/wave/invalid/smpl_chunk_fields_out_of_range.wav +0 -0
  29. data/test/fixtures/wave/invalid/smpl_chunk_loop_count_too_high.wav +0 -0
  30. data/test/fixtures/wave/invalid/smpl_chunk_truncated_sampler_specific_data.wav +0 -0
  31. data/test/fixtures/wave/invalid/truncated_smpl_chunk.wav +0 -0
  32. data/test/fixtures/wave/unsupported/bad_audio_format.wav +0 -0
  33. data/test/fixtures/wave/unsupported/bad_sample_rate.wav +0 -0
  34. data/test/fixtures/wave/unsupported/extensible_unsupported_subformat_guid.wav +0 -0
  35. data/test/fixtures/wave/unsupported/unsupported_audio_format.wav +0 -0
  36. data/test/fixtures/wave/unsupported/unsupported_bits_per_sample.wav +0 -0
  37. data/test/fixtures/wave/valid/valid_mono_pcm_16_44100_junk_chunk_final_chunk_missing_padding_byte.wav +0 -0
  38. data/test/fixtures/wave/valid/valid_mono_pcm_16_44100_junk_chunk_with_padding_byte.wav +0 -0
  39. data/test/fixtures/wave/valid/valid_mono_pcm_8_44100_with_padding_byte.wav +0 -0
  40. data/test/fixtures/wave/valid/valid_with_sample_chunk_after_data_chunk.wav +0 -0
  41. data/test/fixtures/wave/valid/valid_with_sample_chunk_after_data_chunk_and_data_chunk_has_padding_byte.wav +0 -0
  42. data/test/fixtures/wave/valid/valid_with_sample_chunk_before_data_chunk.wav +0 -0
  43. data/test/fixtures/wave/valid/valid_with_sample_chunk_no_loops.wav +0 -0
  44. data/test/fixtures/wave/valid/valid_with_sample_chunk_with_extra_unused_bytes.wav +0 -0
  45. data/test/fixtures/wave/valid/valid_with_sample_chunk_with_sampler_specific_data.wav +0 -0
  46. data/test/format_test.rb +4 -4
  47. data/test/reader_test.rb +266 -8
  48. data/test/sampler_info_test.rb +314 -0
  49. data/test/sampler_loop_test.rb +215 -0
  50. data/test/smpte_timecode_test.rb +103 -0
  51. data/test/writer_test.rb +1 -1
  52. metadata +30 -6
  53. data/lib/wavefile/chunk_readers/generic_chunk_reader.rb +0 -15
  54. data/lib/wavefile/chunk_readers/riff_chunk_reader.rb +0 -19
@@ -93,7 +93,9 @@ module WaveFile
93
93
  # writer.write(buffer)
94
94
  #
95
95
  # Returns the number of sample frames that have been written to the file so far.
96
+ #
96
97
  # Raises WriterClosedError if the Writer has been closed.
98
+ #
97
99
  # Raises BufferConversionError if the Buffer can't be converted to the Writer's format.
98
100
  def write(buffer)
99
101
  if @closed
@@ -287,10 +289,14 @@ module WaveFile
287
289
  requires_fact_chunk = (format_code != :pcm)
288
290
 
289
291
  sample_data_byte_count = sample_frame_count * @format.block_align
292
+ riff_chunk_size = CANONICAL_HEADER_BYTE_LENGTH[format_code] + sample_data_byte_count
293
+ if sample_data_byte_count.odd?
294
+ riff_chunk_size += 1
295
+ end
290
296
 
291
297
  # Write the header for the RIFF chunk
292
298
  header = CHUNK_IDS[:riff]
293
- header += [CANONICAL_HEADER_BYTE_LENGTH[format_code] + sample_data_byte_count].pack(UNSIGNED_INT_32)
299
+ header += [riff_chunk_size].pack(UNSIGNED_INT_32)
294
300
  header += WAVEFILE_FORMAT_CODE
295
301
 
296
302
  # Write the format chunk
@@ -1 +1 @@
1
- RIFF
1
+ RIFF
data/test/format_test.rb CHANGED
@@ -14,7 +14,7 @@ class FormatTest < Minitest::Test
14
14
  end
15
15
 
16
16
  def test_invalid_channels
17
- ["dsfsfsdf", :foo, 0, -1, 65536, 2.5, 2.0].each do |invalid_channels|
17
+ ["dsfsfsdf", "1", :foo, 0, -1, 65536, 2.5, 2.0].each do |invalid_channels|
18
18
  assert_raises(InvalidFormatError) { Format.new(invalid_channels, :pcm_16, 44100) }
19
19
  end
20
20
  end
@@ -30,7 +30,7 @@ class FormatTest < Minitest::Test
30
30
  end
31
31
 
32
32
  def test_invalid_sample_format
33
- ["dsfsfsdf", :foo, :pcm, 0, 12, :pcm_14, :pcm_abc, :float_40].each do |invalid_sample_format|
33
+ ["dsfsfsdf", "pcm_16", :foo, :pcm, 0, 12, :pcm_14, :pcm_abc, :float_40].each do |invalid_sample_format|
34
34
  assert_raises(InvalidFormatError) { Format.new(:mono, invalid_sample_format, 44100) }
35
35
  end
36
36
  end
@@ -46,13 +46,13 @@ class FormatTest < Minitest::Test
46
46
  end
47
47
 
48
48
  def test_valid_sample_rate
49
- [1, 44100, 4294967296].each do |valid_sample_rate|
49
+ [1, 44100, 4294967295].each do |valid_sample_rate|
50
50
  assert_equal(valid_sample_rate, Format.new(:mono, :pcm_16, valid_sample_rate).sample_rate)
51
51
  end
52
52
  end
53
53
 
54
54
  def test_invalid_sample_rate
55
- ["dsfsfsdf", :foo, 0, -1, 4294967297, 44100.5, 44100.0].each do |invalid_sample_rate|
55
+ ["dsfsfsdf", "44100", :foo, 0, -1, 4294967296, 44100.5, 44100.0].each do |invalid_sample_rate|
56
56
  assert_raises(InvalidFormatError) { Format.new(:mono, :pcm_16, invalid_sample_rate) }
57
57
  end
58
58
  end
data/test/reader_test.rb CHANGED
@@ -22,15 +22,27 @@ class ReaderTest < Minitest::Test
22
22
  # File consists of "RIFF" and nothing else
23
23
  "invalid/incomplete_riff_header.wav",
24
24
 
25
+ # The RIFF header chunk size field ends prematurely
26
+ "invalid/riff_chunk_has_incomplete_chunk_size.wav",
27
+
25
28
  # First 4 bytes in the file are not "RIFF"
26
29
  "invalid/bad_riff_header.wav",
27
30
 
31
+ # The format code in the RIFF header is missing
32
+ "invalid/no_riff_format.wav",
33
+
34
+ # The format code in the RIFF header is truncated; i.e. not a full 4 bytes
35
+ "invalid/incomplete_riff_format.wav",
36
+
28
37
  # The format code in the RIFF header is not "WAVE"
29
38
  "invalid/bad_wavefile_format.wav",
30
39
 
31
40
  # The file consists of just a valid RIFF header
32
41
  "invalid/no_format_chunk.wav",
33
42
 
43
+ # The format chunk only includes the chunk ID
44
+ "invalid/no_format_chunk_size.wav",
45
+
34
46
  # The format chunk has 0 bytes in it (despite the chunk size being 16)
35
47
  "invalid/empty_format_chunk.wav",
36
48
 
@@ -39,6 +51,27 @@ class ReaderTest < Minitest::Test
39
51
 
40
52
  # The RIFF header and format chunk are OK, but there is no data chunk
41
53
  "invalid/no_data_chunk.wav",
54
+
55
+ # The data chunk only contains the chunk ID, nothing else
56
+ "invalid/data_chunk_ends_after_chunk_id.wav",
57
+
58
+ # The data chunk size field ends prematurely
59
+ "invalid/data_chunk_has_incomplete_chunk_size.wav",
60
+
61
+ # The format chunk comes after the data chunk; it must come before
62
+ "invalid/format_chunk_after_data_chunk.wav",
63
+
64
+ # Contains a `smpl` chunk that has a size of 0 and no data
65
+ "invalid/smpl_chunk_empty.wav",
66
+
67
+ # Contains a `smpl` chunk that doesn't have enough bytes to match the chunk's size
68
+ "invalid/truncated_smpl_chunk.wav",
69
+
70
+ # `smpl` chunk does not contain as many loops as the 'loop count' field indicates
71
+ "invalid/smpl_chunk_loop_count_too_high.wav",
72
+
73
+ # `smpl` chunk does not contain as bytes as 'sampler specific data size' field indicates
74
+ "invalid/smpl_chunk_truncated_sampler_specific_data.wav",
42
75
  ]
43
76
 
44
77
  invalid_fixtures.each do |fixture_name|
@@ -66,8 +99,9 @@ class ReaderTest < Minitest::Test
66
99
  assert_nil(reader.format.speaker_mapping)
67
100
  assert_equal(false, reader.closed?)
68
101
  assert_equal(0, reader.current_sample_frame)
69
- assert_equal(2240, reader.total_sample_frames)
102
+ assert_equal(0, reader.total_sample_frames)
70
103
  assert_equal(false, reader.readable_format?)
104
+ assert_nil(reader.sampler_info)
71
105
  reader.close
72
106
  end
73
107
 
@@ -84,8 +118,9 @@ class ReaderTest < Minitest::Test
84
118
  assert_equal([:front_center], reader.format.speaker_mapping)
85
119
  assert_equal(false, reader.closed?)
86
120
  assert_equal(0, reader.current_sample_frame)
87
- assert_equal(2240, reader.total_sample_frames)
121
+ assert_equal(0, reader.total_sample_frames)
88
122
  assert_equal(false, reader.readable_format?)
123
+ assert_nil(reader.sampler_info)
89
124
  reader.close
90
125
  end
91
126
  end
@@ -146,6 +181,7 @@ class ReaderTest < Minitest::Test
146
181
  assert_equal(0, reader.current_sample_frame)
147
182
  assert_equal(2240, reader.total_sample_frames)
148
183
  assert_equal(true, reader.readable_format?)
184
+ assert_nil(reader.sampler_info)
149
185
  reader.close
150
186
  end
151
187
 
@@ -162,6 +198,7 @@ class ReaderTest < Minitest::Test
162
198
  assert_equal(0, reader.current_sample_frame)
163
199
  assert_equal(2240, reader.total_sample_frames)
164
200
  assert_equal(true, reader.readable_format?)
201
+ assert_nil(reader.sampler_info)
165
202
  reader.close
166
203
  end
167
204
 
@@ -178,6 +215,7 @@ class ReaderTest < Minitest::Test
178
215
  assert_equal(1024, reader.current_sample_frame)
179
216
  assert_equal(2240, reader.total_sample_frames)
180
217
  assert_equal(true, reader.readable_format?)
218
+ assert_nil(reader.sampler_info)
181
219
  end
182
220
  end
183
221
  end
@@ -211,6 +249,7 @@ class ReaderTest < Minitest::Test
211
249
  assert_equal(0, reader.current_sample_frame)
212
250
  assert_equal(2240, reader.total_sample_frames)
213
251
  assert_equal(true, reader.readable_format?)
252
+ assert_nil(reader.sampler_info)
214
253
  reader.close
215
254
 
216
255
  buffers = read_file("valid/valid_extensible_stereo_pcm_16_44100.wav", 1024)
@@ -237,6 +276,7 @@ class ReaderTest < Minitest::Test
237
276
  assert_equal(0, reader.current_sample_frame)
238
277
  assert_equal(2240, reader.total_sample_frames)
239
278
  assert_equal(true, reader.readable_format?)
279
+ assert_nil(reader.sampler_info)
240
280
  reader.close
241
281
 
242
282
  buffers = read_file("valid/valid_extensible_stereo_pcm_24_44100_no_speaker_mapping.wav", 1024)
@@ -263,6 +303,7 @@ class ReaderTest < Minitest::Test
263
303
  assert_equal(0, reader.current_sample_frame)
264
304
  assert_equal(2240, reader.total_sample_frames)
265
305
  assert_equal(true, reader.readable_format?)
306
+ assert_nil(reader.sampler_info)
266
307
  reader.close
267
308
 
268
309
  buffers = read_file("valid/valid_extensible_stereo_pcm_16_44100_more_speakers_than_channels.wav", 1024)
@@ -307,6 +348,7 @@ class ReaderTest < Minitest::Test
307
348
  assert_equal(0, reader.current_sample_frame)
308
349
  assert_equal(2240, reader.total_sample_frames)
309
350
  assert_equal(true, reader.readable_format?)
351
+ assert_nil(reader.sampler_info)
310
352
  reader.close
311
353
 
312
354
  buffers = read_file("valid/valid_extensible_stereo_pcm_16_44100_more_speakers_than_defined_by_spec.wav", 1024)
@@ -333,6 +375,7 @@ class ReaderTest < Minitest::Test
333
375
  assert_equal(0, reader.current_sample_frame)
334
376
  assert_equal(2240, reader.total_sample_frames)
335
377
  assert_equal(true, reader.readable_format?)
378
+ assert_nil(reader.sampler_info)
336
379
  reader.close
337
380
 
338
381
  buffers = read_file("valid/valid_extensible_stereo_pcm_16_44100_only_undefined_high_bit_speakers.wav", 1024)
@@ -362,6 +405,7 @@ class ReaderTest < Minitest::Test
362
405
  assert_equal(0, reader.current_sample_frame)
363
406
  assert_equal(2240, reader.total_sample_frames)
364
407
  assert_equal(true, reader.readable_format?)
408
+ assert_nil(reader.sampler_info)
365
409
  reader.close
366
410
 
367
411
  buffers = read_file("valid/valid_mono_pcm_16_44100_with_extension.wav", 1024)
@@ -387,13 +431,34 @@ class ReaderTest < Minitest::Test
387
431
  buffers = read_file("valid/valid_mono_pcm_8_44100_with_padding_byte.wav", 1024)
388
432
 
389
433
  assert_equal(3, buffers.length)
390
- assert_equal([1024, 1024, 191], buffers.map {|buffer| buffer.samples.length })
434
+ assert_equal([1024, 1024, 193], buffers.map {|buffer| buffer.samples.length })
391
435
  assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
392
436
  assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
393
- assert_equal((SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 23) + [88, 88, 88, 88, 167, 167, 167],
437
+ assert_equal((SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24) + [88],
394
438
  buffers[2].samples)
395
439
  end
396
440
 
441
+ def test_read_truncated_file
442
+ reader = Reader.new(fixture("invalid/data_chunk_truncated.wav"), Format.new(:mono, :pcm_8, 44100))
443
+
444
+ # The chunk does not actually contain this many sample frames, it actually has 2240
445
+ assert_equal(100000, reader.total_sample_frames)
446
+ assert_equal(0, reader.current_sample_frame)
447
+
448
+ # First set of requested sample frames should be read correctly
449
+ buffer = reader.read(2000)
450
+ assert_equal(2000, buffer.samples.length)
451
+ assert_equal(2000, reader.current_sample_frame)
452
+
453
+ # All of the remaining sample frames are returned, which is fewer than were requested.
454
+ buffer = reader.read(2000)
455
+ assert_equal(240, buffer.samples.length)
456
+ assert_equal(2240, reader.current_sample_frame)
457
+
458
+ # Since there are no more sample frames, an end-of-file error should be raised
459
+ assert_raises(EOFError) { reader.read(2000) }
460
+ end
461
+
397
462
  def test_each_buffer_no_block_given
398
463
  reader = Reader.new(fixture("valid/valid_mono_pcm_16_44100.wav"))
399
464
  assert_raises(LocalJumpError) { reader.each_buffer(1024) }
@@ -457,13 +522,13 @@ class ReaderTest < Minitest::Test
457
522
  reader.each_buffer(1024) {|buffer| buffers << buffer }
458
523
 
459
524
  assert_equal(3, buffers.length)
460
- assert_equal([1024, 1024, 191], buffers.map {|buffer| buffer.samples.length })
525
+ assert_equal([1024, 1024, 193], buffers.map {|buffer| buffer.samples.length })
461
526
  assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
462
527
  assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
463
- assert_equal((SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 23) + [88, 88, 88, 88, 167, 167, 167],
528
+ assert_equal((SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24) + [88],
464
529
  buffers[2].samples)
465
- assert_equal(2239, reader.current_sample_frame)
466
- assert_equal(2239, reader.total_sample_frames)
530
+ assert_equal(2241, reader.current_sample_frame)
531
+ assert_equal(2241, reader.total_sample_frames)
467
532
  end
468
533
 
469
534
  def test_each_buffer_inside_reader_block
@@ -492,6 +557,17 @@ class ReaderTest < Minitest::Test
492
557
  assert_equal(2240, reader.total_sample_frames)
493
558
  end
494
559
 
560
+ def test_read_non_data_chunk_is_final_chunk_without_padding_byte
561
+ # This fixture file contains a JUNK chunk with an odd size, but no padding byte. When a chunk
562
+ # is the final chunk in the file, a missing padding byte won't cause an error as long as the
563
+ # RIFF chunk size field matches the actual number of bytes in the file.
564
+ reader = Reader.new(fixture("valid/valid_mono_pcm_16_44100_junk_chunk_final_chunk_missing_padding_byte.wav"))
565
+ buffer = reader.read(1024)
566
+ assert_equal(buffer.samples, SQUARE_WAVE_CYCLE[:mono][:pcm_16] * 128)
567
+ assert_equal(1024, reader.current_sample_frame)
568
+ assert_equal(2240, reader.total_sample_frames)
569
+ end
570
+
495
571
  def test_closed?
496
572
  reader = Reader.new(fixture("valid/valid_mono_pcm_16_44100.wav"))
497
573
  assert_equal(false, reader.closed?)
@@ -600,6 +676,188 @@ class ReaderTest < Minitest::Test
600
676
  end
601
677
  end
602
678
 
679
+ def test_smpl_chunk
680
+ file_name = fixture("valid/valid_with_sample_chunk_before_data_chunk.wav")
681
+ sampler_info = Reader.new(file_name).sampler_info
682
+
683
+ assert_equal(0, sampler_info.manufacturer_id)
684
+ assert_equal(0, sampler_info.product_id)
685
+ assert_equal(0, sampler_info.sample_nanoseconds)
686
+ assert_equal(60, sampler_info.midi_note)
687
+ assert_equal(50.0, sampler_info.fine_tuning_cents)
688
+ assert_equal(0, sampler_info.smpte_format)
689
+ assert_equal(0, sampler_info.smpte_offset.hours)
690
+ assert_equal(0, sampler_info.smpte_offset.minutes)
691
+ assert_equal(0, sampler_info.smpte_offset.seconds)
692
+ assert_equal(0, sampler_info.smpte_offset.frames)
693
+ assert_equal(1, sampler_info.loops.length)
694
+ assert_equal(0, sampler_info.loops[0].id)
695
+ assert_equal(:backward, sampler_info.loops[0].type)
696
+ assert_equal(0, sampler_info.loops[0].start_sample_frame)
697
+ assert_equal(0, sampler_info.loops[0].end_sample_frame)
698
+ assert_equal(0.5, sampler_info.loops[0].fraction)
699
+ assert_equal(1, sampler_info.loops[0].play_count)
700
+ assert_equal("", sampler_info.sampler_specific_data)
701
+ end
702
+
703
+ # Several field values are out of the expected range, but the file should be successfully
704
+ # read anyway because the sample chunk has the correct structure
705
+ def test_smpl_chunk_field_values_out_of_range
706
+ file_name = fixture("invalid/smpl_chunk_fields_out_of_range.wav")
707
+ sampler_info = Reader.new(file_name).sampler_info
708
+
709
+ assert_equal(0, sampler_info.manufacturer_id)
710
+ assert_equal(0, sampler_info.product_id)
711
+ assert_equal(0, sampler_info.sample_nanoseconds)
712
+ assert_equal(10000, sampler_info.midi_note)
713
+ assert_equal(50.0, sampler_info.fine_tuning_cents)
714
+ assert_equal(99999, sampler_info.smpte_format)
715
+ assert_equal(-128, sampler_info.smpte_offset.hours)
716
+ assert_equal(128, sampler_info.smpte_offset.minutes)
717
+ assert_equal(8, sampler_info.smpte_offset.seconds)
718
+ assert_equal(1, sampler_info.smpte_offset.frames)
719
+ assert_equal(1, sampler_info.loops.length)
720
+ assert_equal(0, sampler_info.loops[0].id)
721
+ assert_equal(88888, sampler_info.loops[0].type)
722
+ assert_equal(9999999, sampler_info.loops[0].start_sample_frame)
723
+ assert_equal(9999999, sampler_info.loops[0].end_sample_frame)
724
+ assert_equal(0.5, sampler_info.loops[0].fraction)
725
+ assert_equal(1, sampler_info.loops[0].play_count)
726
+ assert_equal("", sampler_info.sampler_specific_data)
727
+ end
728
+
729
+ def test_smpl_chunk_after_data_chunk
730
+ file_name = fixture("valid/valid_with_sample_chunk_after_data_chunk.wav")
731
+ sampler_info = Reader.new(file_name).sampler_info
732
+
733
+ assert_equal(0, sampler_info.manufacturer_id)
734
+ assert_equal(0, sampler_info.product_id)
735
+ assert_equal(0, sampler_info.sample_nanoseconds)
736
+ assert_equal(60, sampler_info.midi_note)
737
+ assert_equal(50.0, sampler_info.fine_tuning_cents)
738
+ assert_equal(0, sampler_info.smpte_format)
739
+ assert_equal(0, sampler_info.smpte_offset.hours)
740
+ assert_equal(0, sampler_info.smpte_offset.minutes)
741
+ assert_equal(0, sampler_info.smpte_offset.seconds)
742
+ assert_equal(0, sampler_info.smpte_offset.frames)
743
+ assert_equal(1, sampler_info.loops.length)
744
+ assert_equal(0, sampler_info.loops[0].id)
745
+ assert_equal(:backward, sampler_info.loops[0].type)
746
+ assert_equal(0, sampler_info.loops[0].start_sample_frame)
747
+ assert_equal(0, sampler_info.loops[0].end_sample_frame)
748
+ assert_equal(0.5, sampler_info.loops[0].fraction)
749
+ assert_equal(1, sampler_info.loops[0].play_count)
750
+ assert_equal("", sampler_info.sampler_specific_data)
751
+ end
752
+
753
+ def test_smpl_chunk_after_data_chunk_and_data_chunk_has_padding_byte
754
+ file_name = fixture("valid/valid_with_sample_chunk_after_data_chunk_and_data_chunk_has_padding_byte.wav")
755
+
756
+ reader = Reader.new(file_name)
757
+ sampler_info = reader.sampler_info
758
+
759
+ # Should correctly deal with data chunk with odd number of bytes, followed
760
+ # by a padding byte.
761
+ assert_equal(2241, reader.total_sample_frames)
762
+ # Test that data chunk read is correctly queued up to start of data chunk
763
+ assert_equal([88, 88, 88, 88, 167, 167, 167, 167], reader.read(8).samples)
764
+
765
+ # Sample chunk should be correctly located despite the padding byte following
766
+ # the data chunk.
767
+ assert_equal(0, sampler_info.manufacturer_id)
768
+ assert_equal(0, sampler_info.product_id)
769
+ assert_equal(0, sampler_info.sample_nanoseconds)
770
+ assert_equal(60, sampler_info.midi_note)
771
+ assert_equal(50.0, sampler_info.fine_tuning_cents)
772
+ assert_equal(0, sampler_info.smpte_format)
773
+ assert_equal(0, sampler_info.smpte_offset.hours)
774
+ assert_equal(0, sampler_info.smpte_offset.minutes)
775
+ assert_equal(0, sampler_info.smpte_offset.seconds)
776
+ assert_equal(0, sampler_info.smpte_offset.frames)
777
+ assert_equal(1, sampler_info.loops.length)
778
+ assert_equal(0, sampler_info.loops[0].id)
779
+ assert_equal(:backward, sampler_info.loops[0].type)
780
+ assert_equal(0, sampler_info.loops[0].start_sample_frame)
781
+ assert_equal(0, sampler_info.loops[0].end_sample_frame)
782
+ assert_equal(0.5, sampler_info.loops[0].fraction)
783
+ assert_equal(1, sampler_info.loops[0].play_count)
784
+ assert_equal("", sampler_info.sampler_specific_data)
785
+ end
786
+
787
+ def test_smpl_chunk_with_sampler_specific_data
788
+ file_name = fixture("valid/valid_with_sample_chunk_with_sampler_specific_data.wav")
789
+ sampler_info = Reader.new(file_name).sampler_info
790
+
791
+ assert_equal(0, sampler_info.manufacturer_id)
792
+ assert_equal(0, sampler_info.product_id)
793
+ assert_equal(0, sampler_info.sample_nanoseconds)
794
+ assert_equal(60, sampler_info.midi_note)
795
+ assert_equal(50.0, sampler_info.fine_tuning_cents)
796
+ assert_equal(0, sampler_info.smpte_format)
797
+ assert_equal(0, sampler_info.smpte_offset.hours)
798
+ assert_equal(0, sampler_info.smpte_offset.minutes)
799
+ assert_equal(0, sampler_info.smpte_offset.seconds)
800
+ assert_equal(0, sampler_info.smpte_offset.frames)
801
+ assert_equal(1, sampler_info.loops.length)
802
+ assert_equal(0, sampler_info.loops[0].id)
803
+ assert_equal(:backward, sampler_info.loops[0].type)
804
+ assert_equal(0, sampler_info.loops[0].start_sample_frame)
805
+ assert_equal(0, sampler_info.loops[0].end_sample_frame)
806
+ assert_equal(0.5, sampler_info.loops[0].fraction)
807
+ assert_equal(Float::INFINITY, sampler_info.loops[0].play_count)
808
+ assert_equal("\x04\x01\x03\x02", sampler_info.sampler_specific_data)
809
+ assert_equal(Encoding::ASCII_8BIT, sampler_info.sampler_specific_data.encoding)
810
+ end
811
+
812
+ def test_smpl_chunk_with_extra_unused_bytes
813
+ file_name = fixture("valid/valid_with_sample_chunk_with_extra_unused_bytes.wav")
814
+ reader = Reader.new(file_name)
815
+ sampler_info = reader.sampler_info
816
+
817
+ assert_equal(0, sampler_info.manufacturer_id)
818
+ assert_equal(0, sampler_info.product_id)
819
+ assert_equal(0, sampler_info.sample_nanoseconds)
820
+ assert_equal(60, sampler_info.midi_note)
821
+ assert_equal(50.0, sampler_info.fine_tuning_cents)
822
+ assert_equal(0, sampler_info.smpte_format)
823
+ assert_equal(0, sampler_info.smpte_offset.hours)
824
+ assert_equal(0, sampler_info.smpte_offset.minutes)
825
+ assert_equal(0, sampler_info.smpte_offset.seconds)
826
+ assert_equal(0, sampler_info.smpte_offset.frames)
827
+ assert_equal(1, sampler_info.loops.length)
828
+ assert_equal(0, sampler_info.loops[0].id)
829
+ assert_equal(:backward, sampler_info.loops[0].type)
830
+ assert_equal(0, sampler_info.loops[0].start_sample_frame)
831
+ assert_equal(0, sampler_info.loops[0].end_sample_frame)
832
+ assert_equal(0.5, sampler_info.loops[0].fraction)
833
+ assert_equal(1, sampler_info.loops[0].play_count)
834
+ assert_equal("\x04\x01\x05", sampler_info.sampler_specific_data)
835
+ assert_equal(Encoding::ASCII_8BIT, sampler_info.sampler_specific_data.encoding)
836
+
837
+ # Data chunk should be queued correctly and not raise an error, despite extra bytes
838
+ # at end of `smpl` chunk.
839
+ buffer = reader.read(1)
840
+ assert_equal([[-10000, -10000]], buffer.samples)
841
+ end
842
+
843
+ def test_smpl_chunk_no_loops
844
+ file_name = fixture("valid/valid_with_sample_chunk_no_loops.wav")
845
+ sampler_info = Reader.new(file_name).sampler_info
846
+
847
+ assert_equal(0, sampler_info.manufacturer_id)
848
+ assert_equal(0, sampler_info.product_id)
849
+ assert_equal(0, sampler_info.sample_nanoseconds)
850
+ assert_equal(60, sampler_info.midi_note)
851
+ assert_equal(50.0, sampler_info.fine_tuning_cents)
852
+ assert_equal(0, sampler_info.smpte_format)
853
+ assert_equal(0, sampler_info.smpte_offset.hours)
854
+ assert_equal(0, sampler_info.smpte_offset.minutes)
855
+ assert_equal(0, sampler_info.smpte_offset.seconds)
856
+ assert_equal(0, sampler_info.smpte_offset.frames)
857
+ assert_equal([], sampler_info.loops)
858
+ assert_equal("", sampler_info.sampler_specific_data)
859
+ end
860
+
603
861
  private
604
862
 
605
863
  def read_file(file_name, buffer_size, format=nil)