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