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.
- checksums.yaml +15 -0
- data/LICENSE +1 -1
- data/README.markdown +71 -18
- data/lib/wavefile/buffer.rb +63 -39
- data/lib/wavefile/duration.rb +34 -0
- data/lib/wavefile/format.rb +42 -16
- data/lib/wavefile/info.rb +5 -38
- data/lib/wavefile/reader.rb +88 -61
- data/lib/wavefile/writer.rb +57 -25
- data/lib/wavefile.rb +6 -4
- data/test/buffer_test.rb +227 -37
- data/test/duration_test.rb +73 -0
- data/test/fixtures/actual_output/{valid_mono_8_44100_with_padding_byte.wav → valid_mono_pcm_8_44100_with_padding_byte.wav} +0 -0
- data/test/fixtures/{expected_output → valid}/no_samples.wav +0 -0
- data/test/fixtures/valid/valid_mono_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_float_64_44100.wav +0 -0
- data/test/fixtures/{expected_output/valid_mono_16_44100.wav → valid/valid_mono_pcm_16_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_mono_32_44100.wav → valid/valid_mono_pcm_32_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_mono_8_44100.wav → valid/valid_mono_pcm_8_44100.wav} +0 -0
- 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
- data/test/fixtures/valid/valid_stereo_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_stereo_float_64_44100.wav +0 -0
- data/test/fixtures/{expected_output/valid_stereo_16_44100.wav → valid/valid_stereo_pcm_16_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_stereo_32_44100.wav → valid/valid_stereo_pcm_32_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_stereo_8_44100.wav → valid/valid_stereo_pcm_8_44100.wav} +0 -0
- data/test/fixtures/valid/valid_tri_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_tri_float_64_44100.wav +0 -0
- data/test/fixtures/{expected_output/valid_tri_16_44100.wav → valid/valid_tri_pcm_16_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_tri_32_44100.wav → valid/valid_tri_pcm_32_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_tri_8_44100.wav → valid/valid_tri_pcm_8_44100.wav} +0 -0
- data/test/format_test.rb +88 -58
- data/test/info_test.rb +9 -37
- data/test/reader_test.rb +160 -63
- data/test/wavefile_io_test_helper.rb +40 -30
- data/test/writer_test.rb +124 -37
- metadata +29 -31
- data/test/fixtures/valid/valid_mono_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_8_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_8_44100_with_padding_byte.wav +0 -0
- data/test/fixtures/valid/valid_stereo_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_stereo_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_stereo_8_44100.wav +0 -0
- data/test/fixtures/valid/valid_tri_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_tri_32_44100.wav +0 -0
- 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
data/README.markdown
CHANGED
@@ -1,31 +1,79 @@
|
|
1
|
-
A pure Ruby gem for reading and writing sound files in Wave format (*.wav).
|
1
|
+
A pure Ruby gem for reading and writing sound files in Wave format (*.wav).
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
8
|
+
This is a short example that shows how to append three separate Wave files into a single file:
|
14
9
|
|
15
|
-
|
16
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
#
|
99
|
+
# Contributing
|
51
100
|
|
52
|
-
|
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
|
data/lib/wavefile/buffer.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
82
|
+
samples
|
88
83
|
end
|
89
84
|
|
90
|
-
def
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
data/lib/wavefile/format.rb
CHANGED
@@ -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
|
-
|
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,
|
15
|
-
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
|
-
|
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
|
-
|
34
|
+
def mono?
|
35
|
+
@channels == 1
|
29
36
|
end
|
30
37
|
|
31
|
-
def stereo?
|
32
|
-
|
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
|
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.
|
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
|
-
"
|
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,
|
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
|
-
@
|
12
|
-
|
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
|
-
:
|
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
|