wavefile 0.4.0 → 0.5.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 (46) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +1 -1
  3. data/README.markdown +71 -18
  4. data/lib/wavefile/buffer.rb +63 -39
  5. data/lib/wavefile/duration.rb +34 -0
  6. data/lib/wavefile/format.rb +42 -16
  7. data/lib/wavefile/info.rb +5 -38
  8. data/lib/wavefile/reader.rb +88 -61
  9. data/lib/wavefile/writer.rb +57 -25
  10. data/lib/wavefile.rb +6 -4
  11. data/test/buffer_test.rb +227 -37
  12. data/test/duration_test.rb +73 -0
  13. data/test/fixtures/actual_output/{valid_mono_8_44100_with_padding_byte.wav → valid_mono_pcm_8_44100_with_padding_byte.wav} +0 -0
  14. data/test/fixtures/{expected_output → valid}/no_samples.wav +0 -0
  15. data/test/fixtures/valid/valid_mono_float_32_44100.wav +0 -0
  16. data/test/fixtures/valid/valid_mono_float_64_44100.wav +0 -0
  17. data/test/fixtures/{expected_output/valid_mono_16_44100.wav → valid/valid_mono_pcm_16_44100.wav} +0 -0
  18. data/test/fixtures/{expected_output/valid_mono_32_44100.wav → valid/valid_mono_pcm_32_44100.wav} +0 -0
  19. data/test/fixtures/{expected_output/valid_mono_8_44100.wav → valid/valid_mono_pcm_8_44100.wav} +0 -0
  20. data/test/fixtures/{expected_output/valid_mono_8_44100_with_padding_byte.wav → valid/valid_mono_pcm_8_44100_with_padding_byte.wav} +0 -0
  21. data/test/fixtures/valid/valid_stereo_float_32_44100.wav +0 -0
  22. data/test/fixtures/valid/valid_stereo_float_64_44100.wav +0 -0
  23. data/test/fixtures/{expected_output/valid_stereo_16_44100.wav → valid/valid_stereo_pcm_16_44100.wav} +0 -0
  24. data/test/fixtures/{expected_output/valid_stereo_32_44100.wav → valid/valid_stereo_pcm_32_44100.wav} +0 -0
  25. data/test/fixtures/{expected_output/valid_stereo_8_44100.wav → valid/valid_stereo_pcm_8_44100.wav} +0 -0
  26. data/test/fixtures/valid/valid_tri_float_32_44100.wav +0 -0
  27. data/test/fixtures/valid/valid_tri_float_64_44100.wav +0 -0
  28. data/test/fixtures/{expected_output/valid_tri_16_44100.wav → valid/valid_tri_pcm_16_44100.wav} +0 -0
  29. data/test/fixtures/{expected_output/valid_tri_32_44100.wav → valid/valid_tri_pcm_32_44100.wav} +0 -0
  30. data/test/fixtures/{expected_output/valid_tri_8_44100.wav → valid/valid_tri_pcm_8_44100.wav} +0 -0
  31. data/test/format_test.rb +88 -58
  32. data/test/info_test.rb +9 -37
  33. data/test/reader_test.rb +160 -63
  34. data/test/wavefile_io_test_helper.rb +40 -30
  35. data/test/writer_test.rb +124 -37
  36. metadata +29 -31
  37. data/test/fixtures/valid/valid_mono_16_44100.wav +0 -0
  38. data/test/fixtures/valid/valid_mono_32_44100.wav +0 -0
  39. data/test/fixtures/valid/valid_mono_8_44100.wav +0 -0
  40. data/test/fixtures/valid/valid_mono_8_44100_with_padding_byte.wav +0 -0
  41. data/test/fixtures/valid/valid_stereo_16_44100.wav +0 -0
  42. data/test/fixtures/valid/valid_stereo_32_44100.wav +0 -0
  43. data/test/fixtures/valid/valid_stereo_8_44100.wav +0 -0
  44. data/test/fixtures/valid/valid_tri_16_44100.wav +0 -0
  45. data/test/fixtures/valid/valid_tri_32_44100.wav +0 -0
  46. data/test/fixtures/valid/valid_tri_8_44100.wav +0 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MWI2M2Y5OWVkZTU1ZWE0MGI0MDcyOTY4ODAyODg0ZjU3YWQ3YWI0Mw==
5
+ data.tar.gz: !binary |-
6
+ YzI5Yzg1YTI2MzgxYTJmYjNkOTk1NGJkYWU4MDM4Nzk1Zjk5MjgxOA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZjVlZjg0NTBjOTFhMTI2NjFlZWMzNzVlOTc5NjY4MjFjMDI5OWNmYmY5MmQ2
10
+ MDE0ZDAxNWUxYWFiYWU5NjRiMWU4MWE5NWQ0ZjBlNTQwNDc4ZDExYmY3NmEz
11
+ M2IxZmRlYTVkNjliODY3OGY2YmY4YjFhYTUzZTllNGU3M2YwNzI=
12
+ data.tar.gz: !binary |-
13
+ YjRmYWVkZDVkYjJmMGI4OTAwOTgzM2RlYTA1OGYwNmYzMGViMjAyNzk4N2Ey
14
+ ZTliMDVkNmNlYWMxNDg4ODA3Y2Q3YTQxZjhiNjQ3YTgwZjFiOGIwMGM1YWJk
15
+ MTg0MGJlMDEwOGRiNGMwY2ZhNjY5NzlhZDQ3ODU1NDlhYzUxNTY=
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  == WaveFile
2
2
 
3
- # Copyright (c) 2009-12 Joel Strait
3
+ # Copyright (c) 2009-13 Joel Strait
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person
6
6
  # obtaining a copy of this software and associated documentation
data/README.markdown CHANGED
@@ -1,31 +1,79 @@
1
- A pure Ruby gem for reading and writing sound files in Wave format (*.wav). You can use this gem to create Ruby programs that produce audio, such as [drum machine](http://beatsdrummachine.com).
1
+ A pure Ruby gem for reading and writing sound files in Wave format (*.wav).
2
2
 
3
- # What's New in v0.4.0?
3
+ You can use this gem to create Ruby programs that produce audio, such as [drum machine](http://beatsdrummachine.com). Since it is written in pure Ruby (as opposed to wrapping an existing C library), you can use it without having to compile a separate extension.
4
4
 
5
- This version is a re-write with a completely new, much improved API. (The old API has been removed). Some improvements due to the new API include:
6
5
 
7
- * Reduced memory consumption, due to not having to load the entire file into memory. In practice, this allows the gem to read/write files that previously would have been prohibitively large.
8
- * Better performance for large files, for the same reason as above.
9
- * Ability to progressively append data to the end of a file, instead of writing the entire file at once.
10
- * Ability to easily read and write data in an arbitrary format, regardless of the file's native format. For example, you can transparently read data out of a 16-bit stereo file as 8-bit mono.
11
- * Automatic file management, similar to how IO.open() works. It's easy to continually read the sample data from a file, passing each buffer to a block, and have the file automatically close when there is no more data left.
6
+ # Example Usage
12
7
 
13
- Other improvements include:
8
+ This is a short example that shows how to append three separate Wave files into a single file:
14
9
 
15
- * Ability to query format metadata of files without opening them, even for formats that this gem can't read or write.
16
- * Support for reading and writing 32-bit PCM files.
10
+ require 'wavefile'
11
+ include WaveFile
12
+
13
+ FILES_TO_APPEND = ["file1.wav", "file2.wav", "file3.wav"]
14
+ SAMPLES_PER_BUFFER = 4096
15
+
16
+ Writer.new("append.wav", Format.new(:stereo, :pcm_16, 44100)) do |writer|
17
+ FILES_TO_APPEND.each do |file_name|
18
+ Reader.new(file_name).each_buffer(SAMPLES_PER_BUFFER) do |buffer|
19
+ writer.write(buffer)
20
+ end
21
+ end
22
+ end
23
+
24
+ More examples can be [found on the wiki](https://github.com/jstrait/wavefile/wiki).
25
+
26
+
27
+ # Features
28
+
29
+ * Ability to read and write Wave files with any number of channels in the following formats:
30
+ * PCM (8, 16, and 32 bits per sample)
31
+ * Floating Point (32 and 64 bits per sample)
32
+ * Ability to read sample data from a file in any of the supported formats, regardless of the file's actual sample format
33
+
34
+ # Sample data will be returned as 32-bit floating point samples,
35
+ # regardless of the actual sample format in the file.
36
+ Reader.new("some_file.wav", Format.new(:mono, :float_32, 44100))
37
+
38
+ * Automatic file management, similar to how `IO.open` works. That is, you can open a file for reading or writing, and if a block is given, the file will automatically be closed when the block exits.
17
39
 
18
- However, reading or writing data as floating point (i.e. values between -1.0 and 1.0) won't be supported in v0.4.0 to keep the scope in check. It might be re-added in the future.
40
+ Writer.new("some_file.wav", Format.new(:mono, :pcm_16, 44100)) do |writer|
41
+ # write some sample data
42
+ end
43
+ # At this point, the writer will automatically be closed, no need to do it manually
44
+
45
+ * Ability to query metadata about Wave files (sample rate, number of channels, number of sample frames, etc.), including files that are in a format this gem can't read or write.
46
+ * Pure Ruby, so no need to compile a separate extension in order to use it.
47
+
48
+
49
+ # Current Release: v0.5.0
50
+
51
+ This release includes these improvements:
52
+
53
+ * Support for reading and writing Wave files containing 32 and 64-bit floating point sample data.
54
+ * Support for buffers that contain floating point data (i.e., samples between -1.0 and 1.0), including the ability to convert to and from PCM buffers.
55
+ * A new `Duration` object which can be used to calculate the playback time given a sample rate and number of sample frames.
56
+ * New attributes: `Reader.current_sample_frame`, `Reader.total_sample_frames`, and `Writer.total_sample_frames`.
57
+ * Ability to get these attributes as a `Duration` object as well: `Reader.total_duration`, `Writer.total_duration`.
58
+ * The 2nd argument to `Format.new` now indicates the sample format, not the bits per sample. For example, `:pcm_16` or `:float_32` instead of `8` or `16`. For backwards compatibility, `8`, `16`, and `32` can still be given and will be interpreted as `:pcm_8`, `:pcm_16`, and `:pcm_32`, but this support might be removed in the future.
59
+ * Bug fix: Wave files are no longer corrupted when an unhandled exception occurs inside a `Writer` block. (Thanks to [James Tunnell](https://github.com/jamestunnell) for reporting and fixing this).
60
+ * Bug fix: `Writer.file_name` now returns the file name, instead of always returning nil (Thanks to [James Tunnell](https://github.com/jamestunnell) for reporting this).
61
+
62
+ This release also includes changes that are not backwards compatible with v0.4.0. (Until version v1.0, no guarantees to avoid this will be made, but I'll try to have a good reason before doing so).
63
+
64
+ * `Info.duration` now returns a `Duration` object, instead of a hash.
65
+ * `Info.sample_count` has been renamed `sample_frame_count`.
66
+ * Some constants in the `WaveFile` module have changed. (In general, you should treat these as internal to this gem and not use them in your own program).
19
67
 
20
68
 
21
69
  # Compatibility
22
70
 
23
71
  WaveFile has been tested with these Ruby versions, and appears to be compatible with them:
24
72
 
25
- * MRI 1.9.3, 1.9.2, 1.9.1, 1.8.7
26
- * JRuby 1.6.5
73
+ * MRI 2.0.0, 1.9.3, 1.9.2, 1.9.1, 1.8.7
74
+ * JRuby 1.7.3
27
75
  * Rubinius 1.2.4
28
- * MacRuby 0.10
76
+ * MacRuby 0.12
29
77
 
30
78
  If you find any compatibility issues, please let me know by opening a GitHub issue.
31
79
 
@@ -45,8 +93,13 @@ First, install the WaveFile gem from rubygems.org:
45
93
 
46
94
  require 'wavefile'
47
95
 
48
- Note that if you're installing the gem into the default Ruby that comes pre-installed on MacOS (as opposed to a Ruby installed via [RVM](http://beginrescueend.com/)), you should used `sudo gem install wavefile`. Otherwise you might run into a file permission error.
96
+ Note that if you're installing the gem into the default Ruby that comes pre-installed on MacOS (as opposed to a Ruby installed via [RVM](http://beginrescueend.com/) or [rbenv](https://github.com/sstephenson/rbenv/)), you should used `sudo gem install wavefile`. Otherwise you might run into a file permission error.
97
+
49
98
 
50
- # Usage
99
+ # Contributing
51
100
 
52
- For usage instructions with examples, check out the [wiki](https://github.com/jstrait/wavefile/wiki).
101
+ 1. Fork my repo
102
+ 2. Create a branch for your changes
103
+ 3. Add your changes, and please include tests
104
+ 4. Make sure the tests pass by running `rake test`
105
+ 5. Create a pull request
@@ -32,7 +32,7 @@ module WaveFile
32
32
  # Returns a new Buffer; the existing Buffer is unmodified.
33
33
  def convert(new_format)
34
34
  new_samples = convert_buffer(@samples.dup, @format, new_format)
35
- return Buffer.new(new_samples, new_format)
35
+ Buffer.new(new_samples, new_format)
36
36
  end
37
37
 
38
38
 
@@ -50,25 +50,25 @@ module WaveFile
50
50
  def convert!(new_format)
51
51
  @samples = convert_buffer(@samples, @format, new_format)
52
52
  @format = new_format
53
- return self
53
+ self
54
54
  end
55
55
 
56
56
 
57
57
  # The number of channels the buffer's sample data has
58
58
  def channels
59
- return @format.channels
59
+ @format.channels
60
60
  end
61
61
 
62
62
 
63
63
  # The bits per sample of the buffer's sample data
64
64
  def bits_per_sample
65
- return @format.bits_per_sample
65
+ @format.bits_per_sample
66
66
  end
67
67
 
68
68
 
69
69
  # The sample rate of the buffer's sample data
70
70
  def sample_rate
71
- return @format.sample_rate
71
+ @format.sample_rate
72
72
  end
73
73
 
74
74
  attr_reader :samples
@@ -76,18 +76,15 @@ module WaveFile
76
76
  private
77
77
 
78
78
  def convert_buffer(samples, old_format, new_format)
79
- unless old_format.channels == new_format.channels
80
- samples = convert_buffer_channels(samples, old_format.channels, new_format.channels)
81
- end
82
-
83
- unless old_format.bits_per_sample == new_format.bits_per_sample
84
- samples = convert_buffer_bits_per_sample(samples, old_format.bits_per_sample, new_format.bits_per_sample)
85
- end
79
+ samples = convert_channels(samples, old_format.channels, new_format.channels)
80
+ samples = convert_sample_format(samples, old_format, new_format)
86
81
 
87
- return samples
82
+ samples
88
83
  end
89
84
 
90
- def convert_buffer_channels(samples, old_channels, new_channels)
85
+ def convert_channels(samples, old_channels, new_channels)
86
+ return samples if old_channels == new_channels
87
+
91
88
  # The cases of mono -> stereo and vice-versa are handled specially,
92
89
  # because those conversion methods are faster than the general methods,
93
90
  # and the large majority of wave files are expected to be either mono or stereo.
@@ -106,42 +103,69 @@ module WaveFile
106
103
  "Conversion of sample data from #{old_channels} channels to #{new_channels} channels is unsupported"
107
104
  end
108
105
 
109
- return samples
106
+ samples
107
+ end
108
+
109
+ def convert_sample_format(samples, old_format, new_format)
110
+ return samples if old_format.sample_format == :float && new_format.sample_format == :float
111
+
112
+ if old_format.sample_format == :pcm && new_format.sample_format == :pcm
113
+ convert_sample_format_pcm_to_pcm(samples, old_format.bits_per_sample, new_format.bits_per_sample)
114
+ elsif old_format.sample_format == :pcm && new_format.sample_format == :float
115
+ convert_sample_format_pcm_to_float(samples, old_format.bits_per_sample, new_format.bits_per_sample)
116
+ elsif old_format.sample_format == :float && new_format.sample_format == :pcm
117
+ convert_sample_format_float_to_pcm(samples, old_format.bits_per_sample, new_format.bits_per_sample)
118
+ end
110
119
  end
111
120
 
112
- def convert_buffer_bits_per_sample(samples, old_bits_per_sample, new_bits_per_sample)
121
+ def convert_sample_format_pcm_to_pcm(samples, old_bits_per_sample, new_bits_per_sample)
122
+ return samples if old_bits_per_sample == new_bits_per_sample
123
+
113
124
  shift_amount = (new_bits_per_sample - old_bits_per_sample).abs
114
- more_than_one_channel = (Array === samples.first)
115
125
 
116
126
  if old_bits_per_sample == 8
117
- if more_than_one_channel
118
- samples.map! do |sample|
119
- sample.map! {|sub_sample| (sub_sample - 128) << shift_amount }
120
- end
121
- else
122
- samples.map! {|sample| (sample - 128) << shift_amount }
123
- end
127
+ convert_sample_format_helper(samples) {|sample| (sample - 128) << shift_amount }
124
128
  elsif new_bits_per_sample == 8
125
- if more_than_one_channel
126
- samples.map! do |sample|
127
- sample.map! {|sub_sample| (sub_sample >> shift_amount) + 128 }
128
- end
129
- else
130
- samples.map! {|sample| (sample >> shift_amount) + 128 }
131
- end
129
+ convert_sample_format_helper(samples) {|sample| (sample >> shift_amount) + 128 }
132
130
  else
133
- operator = (new_bits_per_sample > old_bits_per_sample) ? :<< : :>>
134
-
135
- if more_than_one_channel
136
- samples.map! do |sample|
137
- sample.map! {|sub_sample| sub_sample.send(operator, shift_amount) }
138
- end
131
+ if new_bits_per_sample > old_bits_per_sample
132
+ convert_sample_format_helper(samples) {|sample| sample << shift_amount }
139
133
  else
140
- samples.map! {|sample| sample.send(operator, shift_amount) }
134
+ convert_sample_format_helper(samples) {|sample| sample >> shift_amount }
141
135
  end
142
136
  end
137
+ end
138
+
139
+ def convert_sample_format_pcm_to_float(samples, old_bits_per_sample, new_bits_per_sample)
140
+ if old_bits_per_sample == 8
141
+ convert_sample_format_helper(samples) {|sample| (sample - 128).to_f / 128.0 }
142
+ elsif old_bits_per_sample == 16
143
+ convert_sample_format_helper(samples) {|sample| sample.to_f / 32768.0 }
144
+ elsif old_bits_per_sample == 32
145
+ convert_sample_format_helper(samples) {|sample| sample.to_f / 2147483648.0 }
146
+ end
147
+ end
148
+
149
+ def convert_sample_format_float_to_pcm(samples, old_bits_per_sample, new_bits_per_sample)
150
+ if new_bits_per_sample == 8
151
+ convert_sample_format_helper(samples) {|sample| (sample * 127.0).round + 128 }
152
+ elsif new_bits_per_sample == 16
153
+ convert_sample_format_helper(samples) {|sample| (sample * 32767.0).round }
154
+ elsif new_bits_per_sample == 32
155
+ convert_sample_format_helper(samples) {|sample| (sample * 2147483647.0).round }
156
+ end
157
+ end
158
+
159
+ def convert_sample_format_helper(samples, &converter)
160
+ more_than_one_channel = (Array === samples.first)
143
161
 
144
- return samples
162
+ if more_than_one_channel
163
+ samples.map! do |sample|
164
+ sample.map! &converter
165
+ end
166
+ else
167
+ samples.map! &converter
168
+ end
145
169
  end
146
170
  end
147
171
  end
@@ -0,0 +1,34 @@
1
+ module WaveFile
2
+ # Calculates playback time given the number of sample frames and the sample rate.
3
+ class Duration
4
+ def initialize(sample_frame_count, sample_rate)
5
+ @sample_frame_count = sample_frame_count
6
+ @sample_rate = sample_rate
7
+
8
+ sample_frames_per_millisecond = sample_rate / 1000.0
9
+ sample_frames_per_second = sample_rate
10
+ sample_frames_per_minute = sample_frames_per_second * 60
11
+ sample_frames_per_hour = sample_frames_per_minute * 60
12
+ @hours, @minutes, @seconds, @milliseconds = 0, 0, 0, 0
13
+
14
+ if(sample_frame_count >= sample_frames_per_hour)
15
+ @hours = sample_frame_count / sample_frames_per_hour
16
+ sample_frame_count -= sample_frames_per_hour * @hours
17
+ end
18
+
19
+ if(sample_frame_count >= sample_frames_per_minute)
20
+ @minutes = sample_frame_count / sample_frames_per_minute
21
+ sample_frame_count -= sample_frames_per_minute * @minutes
22
+ end
23
+
24
+ if(sample_frame_count >= sample_frames_per_second)
25
+ @seconds = sample_frame_count / sample_frames_per_second
26
+ sample_frame_count -= sample_frames_per_second * @seconds
27
+ end
28
+
29
+ @milliseconds = (sample_frame_count / sample_frames_per_millisecond).floor
30
+ end
31
+
32
+ attr_reader :sample_frame_count, :sample_rate, :hours, :minutes, :seconds, :milliseconds
33
+ end
34
+ end
@@ -2,41 +2,48 @@ module WaveFile
2
2
  class InvalidFormatError < StandardError; end
3
3
 
4
4
  class Format
5
- # Not using ranges because of 1.8.7 performance problems with Range.max()
5
+ # Not using ranges because of 1.8.7 performance problems with Range.max
6
6
  MIN_CHANNELS = 1
7
7
  MAX_CHANNELS = 65535
8
8
 
9
9
  MIN_SAMPLE_RATE = 1
10
10
  MAX_SAMPLE_RATE = 4_294_967_296
11
11
 
12
- SUPPORTED_BITS_PER_SAMPLE = [8, 16, 32]
12
+ SUPPORTED_SAMPLE_FORMATS = [:pcm, :float]
13
+ SUPPORTED_BITS_PER_SAMPLE = {
14
+ :pcm => [8, 16, 32],
15
+ :float => [32, 64],
16
+ }
13
17
 
14
- def initialize(channels, bits_per_sample, sample_rate)
15
- channels = canonicalize_channels(channels)
18
+ def initialize(channels, format_code, sample_rate)
19
+ channels = normalize_channels(channels)
20
+ sample_format, bits_per_sample = normalize_format_code(format_code)
16
21
  validate_channels(channels)
17
- validate_bits_per_sample(bits_per_sample)
22
+ validate_sample_format(sample_format)
23
+ validate_bits_per_sample(sample_format, bits_per_sample)
18
24
  validate_sample_rate(sample_rate)
19
25
 
20
26
  @channels = channels
27
+ @sample_format = sample_format
21
28
  @bits_per_sample = bits_per_sample
22
29
  @sample_rate = sample_rate
23
30
  @block_align = (@bits_per_sample / 8) * @channels
24
31
  @byte_rate = @block_align * @sample_rate
25
32
  end
26
33
 
27
- def mono?()
28
- return @channels == 1
34
+ def mono?
35
+ @channels == 1
29
36
  end
30
37
 
31
- def stereo?()
32
- return @channels == 2
38
+ def stereo?
39
+ @channels == 2
33
40
  end
34
41
 
35
- attr_reader :channels, :bits_per_sample, :sample_rate, :block_align, :byte_rate
36
-
42
+ attr_reader :channels, :sample_format, :bits_per_sample, :sample_rate, :block_align, :byte_rate
43
+
37
44
  private
38
45
 
39
- def canonicalize_channels(channels)
46
+ def normalize_channels(channels)
40
47
  if channels == :mono
41
48
  return 1
42
49
  elsif channels == :stereo
@@ -46,17 +53,36 @@ module WaveFile
46
53
  end
47
54
  end
48
55
 
56
+ def normalize_format_code(format_code)
57
+ if SUPPORTED_BITS_PER_SAMPLE[:pcm].include? format_code
58
+ [:pcm, format_code]
59
+ elsif format_code == :float
60
+ [:float, 32]
61
+ else
62
+ sample_format, bits_per_sample = format_code.to_s.split("_")
63
+ [sample_format.to_sym, bits_per_sample.to_i]
64
+ end
65
+ end
66
+
67
+ def validate_sample_format(candidate_sample_format)
68
+ unless SUPPORTED_SAMPLE_FORMATS.include? candidate_sample_format
69
+ raise InvalidFormatError,
70
+ "Sample format of #{candidate_sample_format} is unsupported. " +
71
+ "Only #{SUPPORTED_SAMPLE_FORMATS.inspect} are supported."
72
+ end
73
+ end
74
+
49
75
  def validate_channels(candidate_channels)
50
76
  unless (MIN_CHANNELS..MAX_CHANNELS) === candidate_channels
51
77
  raise InvalidFormatError, "Invalid number of channels. Must be between 1 and #{MAX_CHANNELS}."
52
78
  end
53
79
  end
54
80
 
55
- def validate_bits_per_sample(candidate_bits_per_sample)
56
- unless SUPPORTED_BITS_PER_SAMPLE.member?(candidate_bits_per_sample)
81
+ def validate_bits_per_sample(candidate_sample_format, candidate_bits_per_sample)
82
+ unless SUPPORTED_BITS_PER_SAMPLE[candidate_sample_format].include? candidate_bits_per_sample
57
83
  raise InvalidFormatError,
58
- "Bits per sample of #{candidate_bits_per_sample} is unsupported. " +
59
- "Only #{SUPPORTED_BITS_PER_SAMPLE.inspect} are supported."
84
+ "Bits per sample of #{candidate_bits_per_sample} is unsupported for " +
85
+ "sample format #{candidate_sample_format}."
60
86
  end
61
87
  end
62
88
 
data/lib/wavefile/info.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module WaveFile
2
2
  class Info
3
- def initialize(file_name, raw_format_chunk, sample_count)
3
+ def initialize(file_name, raw_format_chunk, sample_frame_count)
4
4
  @file_name = file_name
5
5
  @audio_format = raw_format_chunk[:audio_format]
6
6
  @channels = raw_format_chunk[:channels]
@@ -8,46 +8,13 @@ module WaveFile
8
8
  @sample_rate = raw_format_chunk[:sample_rate]
9
9
  @byte_rate = raw_format_chunk[:byte_rate]
10
10
  @block_align = raw_format_chunk[:block_align]
11
- @sample_count = sample_count
12
- @duration = calculate_duration()
11
+ @sample_frame_count = sample_frame_count
12
+
13
+ @duration = Duration.new(@sample_frame_count, @sample_rate)
13
14
  end
14
15
 
15
16
  attr_reader :file_name,
16
17
  :audio_format, :channels, :bits_per_sample, :sample_rate, :byte_rate, :block_align,
17
- :sample_count, :duration
18
-
19
- private
20
-
21
- # Calculates playback time given the number of samples and the sample rate.
22
- #
23
- # Returns a hash listing the number of hours, minutes, seconds, and milliseconds of
24
- # playback time.
25
- def calculate_duration()
26
- total_samples = @sample_count
27
- samples_per_millisecond = @sample_rate / 1000.0
28
- samples_per_second = @sample_rate
29
- samples_per_minute = samples_per_second * 60
30
- samples_per_hour = samples_per_minute * 60
31
- hours, minutes, seconds, milliseconds = 0, 0, 0, 0
32
-
33
- if(total_samples >= samples_per_hour)
34
- hours = total_samples / samples_per_hour
35
- total_samples -= samples_per_hour * hours
36
- end
37
-
38
- if(total_samples >= samples_per_minute)
39
- minutes = total_samples / samples_per_minute
40
- total_samples -= samples_per_minute * minutes
41
- end
42
-
43
- if(total_samples >= samples_per_second)
44
- seconds = total_samples / samples_per_second
45
- total_samples -= samples_per_second * seconds
46
- end
47
-
48
- milliseconds = (total_samples / samples_per_millisecond).floor
49
-
50
- @duration = { :hours => hours, :minutes => minutes, :seconds => seconds, :milliseconds => milliseconds }
51
- end
18
+ :sample_frame_count, :duration
52
19
  end
53
20
  end