wavefile 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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