wavefile 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +2 -2
- data/README.markdown +67 -47
- data/Rakefile +23 -0
- data/lib/wavefile.rb +4 -2
- data/lib/wavefile/buffer.rb +40 -25
- data/lib/wavefile/chunk_readers.rb +7 -1
- data/lib/wavefile/chunk_readers/base_chunk_reader.rb +10 -0
- data/lib/wavefile/chunk_readers/data_chunk_reader.rb +77 -0
- data/lib/wavefile/chunk_readers/format_chunk_reader.rb +59 -0
- data/lib/wavefile/chunk_readers/generic_chunk_reader.rb +15 -0
- data/lib/wavefile/chunk_readers/riff_chunk_reader.rb +19 -0
- data/lib/wavefile/chunk_readers/riff_reader.rb +67 -0
- data/lib/wavefile/duration.rb +47 -12
- data/lib/wavefile/format.rb +44 -23
- data/lib/wavefile/reader.rb +101 -111
- data/lib/wavefile/unvalidated_format.rb +36 -6
- data/lib/wavefile/writer.rb +138 -40
- data/test/buffer_test.rb +21 -17
- data/test/chunk_readers/format_chunk_reader_test.rb +130 -0
- data/test/duration_test.rb +42 -1
- data/test/fixtures/actual_output/total_duration_mono_float_32_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_mono_float_64_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_mono_pcm_16_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_mono_pcm_24_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_mono_pcm_32_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_mono_pcm_8_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_stereo_float_32_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_stereo_float_64_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_stereo_pcm_16_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_stereo_pcm_24_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_stereo_pcm_32_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_stereo_pcm_8_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_tri_float_32_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_tri_float_64_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_tri_pcm_16_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_tri_pcm_24_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_tri_pcm_32_44100.wav +0 -0
- data/test/fixtures/actual_output/total_duration_tri_pcm_8_44100.wav +0 -0
- data/test/fixtures/unsupported/README.markdown +1 -1
- data/test/fixtures/unsupported/bad_channel_count.wav +0 -0
- data/test/fixtures/unsupported/extensible_container_size_bigger_than_sample_size.wav +0 -0
- data/test/fixtures/unsupported/extensible_unsupported_subformat_guid.wav +0 -0
- data/test/fixtures/valid/valid_extensible_mono_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_mono_float_64_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_mono_pcm_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_mono_pcm_24_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_mono_pcm_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_mono_pcm_8_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_stereo_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_stereo_float_64_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_stereo_pcm_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_stereo_pcm_24_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_stereo_pcm_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_stereo_pcm_8_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_tri_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_tri_float_64_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_tri_pcm_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_tri_pcm_24_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_tri_pcm_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_extensible_tri_pcm_8_44100.wav +0 -0
- data/test/format_test.rb +22 -48
- data/test/reader_test.rb +188 -93
- data/test/unvalidated_format_test.rb +130 -4
- data/test/wavefile_io_test_helper.rb +6 -4
- data/test/writer_test.rb +118 -95
- metadata +47 -4
- data/lib/wavefile/chunk_readers/header_reader.rb +0 -163
- data/test/fixtures/actual_output/no_samples.wav +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5dccc3baf1df6d96e421a5229bee8937d6a6688e
|
4
|
+
data.tar.gz: 39bc477f86b49caa967ada0eff54625e2288f074
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e13f217ae8eac502c631e910b67ecd3b0aff02c46a3cc7a3149dea75e02563c213eb4511e3a66fec02b62c746b3b8c0cbdb4249423684ddb5a4650396377936f
|
7
|
+
data.tar.gz: 3f5b047ddb807ffc9c5c88e059d2e37b1325147a42ffc562cfae1c854c8c1c711ba2f4c978541c3eccf274cd4b41612e7466e8f35e11db8f6381b7eb49d0995d
|
data/LICENSE
CHANGED
data/README.markdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
A pure Ruby gem for reading and writing sound files in Wave format (*.wav).
|
2
2
|
|
3
|
-
You can use this gem to create Ruby programs that work with 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.
|
3
|
+
You can use this gem to create Ruby programs that work with audio, such as a [command-line 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
5
|
For more info, check out the website: <http://wavefilegem.com/>
|
6
6
|
|
@@ -8,76 +8,66 @@ For more info, check out the website: <http://wavefilegem.com/>
|
|
8
8
|
|
9
9
|
This is a short example that shows how to append three separate Wave files into a single file:
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
11
|
+
```ruby
|
12
|
+
require 'wavefile'
|
13
|
+
include WaveFile
|
14
|
+
|
15
|
+
FILES_TO_APPEND = ["file1.wav", "file2.wav", "file3.wav"]
|
16
|
+
|
17
|
+
Writer.new("append.wav", Format.new(:stereo, :pcm_16, 44100)) do |writer|
|
18
|
+
FILES_TO_APPEND.each do |file_name|
|
19
|
+
Reader.new(file_name).each_buffer do |buffer|
|
20
|
+
writer.write(buffer)
|
23
21
|
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
```
|
24
25
|
|
25
|
-
More examples can be
|
26
|
+
More examples can be found at <http://wavefilegem.com/examples>.
|
26
27
|
|
27
28
|
|
28
29
|
# Features
|
29
30
|
|
30
|
-
|
31
|
-
* PCM (8, 16, 24, and 32 bits per sample)
|
32
|
-
* Floating Point (32 and 64 bits per sample)
|
33
|
-
* Ability to read sample data from a file in any of the supported formats, regardless of the file's actual sample format
|
34
|
-
|
35
|
-
# Sample data will be returned as 32-bit floating point samples,
|
36
|
-
# regardless of the actual sample format in the file.
|
37
|
-
Reader.new("some_file.wav", Format.new(:mono, :float_32, 44100))
|
31
|
+
This gem lets you read and write audio data! You can use it to create Ruby programs that work with sound.
|
38
32
|
|
33
|
+
* Read and write Wave files with any number of channels, in PCM (8/16/24/32 bits per sample) or IEEE Floating Point (32/64 bits per sample) format.
|
34
|
+
* Seamlessly convert between sample formats. Read sample data from a file into any format supported by this gem, regardless of how the sample data is stored in the actual file. Or, create sample data in one format (such as floats between -1.0 and 1.0), but write it to a file in a different format (such as 16-bit PCM).
|
39
35
|
* 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.
|
36
|
+
* 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.
|
37
|
+
* Written in pure Ruby, so it's easy to include in your program. There's no need to compile a separate extension in order to use it.
|
40
38
|
|
41
|
-
Writer.new("some_file.wav", Format.new(:mono, :pcm_16, 44100)) do |writer|
|
42
|
-
# write some sample data
|
43
|
-
end
|
44
|
-
# At this point, the writer will automatically be closed, no need to do it manually
|
45
39
|
|
46
|
-
|
47
|
-
* Pure Ruby, so no need to compile a separate extension in order to use it.
|
40
|
+
# Current Release: v0.8.0
|
48
41
|
|
42
|
+
Released on January 29, 2017, this version includes these changes:
|
49
43
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
* The
|
55
|
-
*
|
56
|
-
* `Reader
|
57
|
-
*
|
58
|
-
*
|
59
|
-
*
|
60
|
-
*
|
61
|
-
*
|
62
|
-
*
|
44
|
+
* Wave files using WAVEFORMATEXTENSIBLE format (format code 65534) can now be read.
|
45
|
+
* Notes/Limitations
|
46
|
+
* The same formats supported in "vanilla" Wave files are supported when reading WAVEFORMATEXTENSIBLE files. That is, PCM (8/16/24/32 bits per sample) or IEEE_FLOAT (32/64 bits per sample).
|
47
|
+
* The channel speaker mapping field is not exposed.
|
48
|
+
* The number of valid bits per sample must match the sample container size. For example, if a file has a sample container size of 24 bits and each sample is 24 bits, then it can be read. If the container size is 32 bits and each sample is 24 bits, it _can't_ be read.
|
49
|
+
* Writing files using WAVEFORMATEXTENSIBLE format is not supported - all files will be written as a "vanilla" file regardless of the number of channels or sample format.
|
50
|
+
* `Reader` and `Writer` can now be constructed using an open `IO` instance, to allow reading/writing using an arbitrary `IO`-like object (`File`, `StringIO`, etc). Previously, they could only be constructed from a file name (given by a String). Thanks to [@taf2](https://github.com/taf2) for suggesting this feature and providing an example pull request.
|
51
|
+
* The buffer size in `Reader.each_buffer()` is now optional. If not given, a default buffer size will be used.
|
52
|
+
* Two `Duration` objects will now evaluate to equal if they represent the same amount of time, due to an overridden definition of `==`. Thanks to [Christopher Smith](https://github.com/chrylis) for suggesting this improvement.
|
53
|
+
* A `ReaderClosedError` is now raised (instead of `IOError`) when attempting to read from a closed `Reader` instance. However, `ReaderClosedError` extends `IOError`.
|
54
|
+
* A `WriterClosedError` is now raised (instead of `IOError`) when attempting to read from a closed `Writer` instance. However, `ReaderClosedError` extends `IOError`.
|
55
|
+
* **Backwards Incompatible Changes**
|
56
|
+
* `Reader.file_name` and `Writer.file_name` have been removed. When a `Reader` or `Writer` instance is constructed from an `IO` instance, this field wouldn't necessarily have a sensible value. Since I don't know of an obvious use-case for these fields, going ahead and removing them altogether.
|
57
|
+
* The long deprecated ability to provide the sample format for a `Format` instance as an integer (implying PCM format) has been removed. For example, this is no longer valid: `Format.new(:mono, 16, 44100)`. Instead, use `Format.new(:mono, :pcm_16, 44100)`.
|
63
58
|
|
64
59
|
|
65
60
|
# Compatibility
|
66
61
|
|
67
62
|
WaveFile has been tested with these Ruby versions, and appears to be compatible with them:
|
68
63
|
|
69
|
-
* MRI 2.3, 2.2, 2.1, 2.0, 1.9.3
|
64
|
+
* MRI 2.4.0, 2.3.3, 2.2.6, 2.1.10, 2.0, 1.9.3
|
70
65
|
|
71
66
|
1.9.3 is the minimum supported Ruby version.
|
72
67
|
|
73
68
|
If you find any compatibility issues, please let me know by opening a GitHub issue.
|
74
69
|
|
75
70
|
|
76
|
-
# Dependencies
|
77
|
-
|
78
|
-
WaveFile has no external dependencies. It is written in pure Ruby, and is entirely self-contained.
|
79
|
-
|
80
|
-
|
81
71
|
# Installation
|
82
72
|
|
83
73
|
First, install the WaveFile gem from rubygems.org:
|
@@ -91,6 +81,36 @@ First, install the WaveFile gem from rubygems.org:
|
|
91
81
|
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://rvm.io/) or [rbenv](https://github.com/sstephenson/rbenv/)), you should used `sudo gem install wavefile`. Otherwise you might run into a file permission error.
|
92
82
|
|
93
83
|
|
84
|
+
# Dependencies
|
85
|
+
|
86
|
+
WaveFile has no external dependencies when used as a gem. It is written in pure Ruby, and is entirely self-contained.
|
87
|
+
|
88
|
+
However, it does have dependencies for local development, in order to run the tests. See below in section "Local Development".
|
89
|
+
|
90
|
+
|
91
|
+
# Local Development
|
92
|
+
|
93
|
+
## Running the Tests
|
94
|
+
|
95
|
+
First, install the required development/test dependencies:
|
96
|
+
|
97
|
+
bundle install
|
98
|
+
|
99
|
+
Then, to run the tests:
|
100
|
+
|
101
|
+
bundle exec rake test
|
102
|
+
|
103
|
+
## Generating test fixtures
|
104
|
+
|
105
|
+
If you want to change one of the fixture `*.wav` files under `/test/fixtures`, edit the appropriate `*.yml` file defined in `/tools`, and then run this:
|
106
|
+
|
107
|
+
rake test:create_fixtures
|
108
|
+
|
109
|
+
## Generating RDoc Documentation
|
110
|
+
|
111
|
+
rake rdoc
|
112
|
+
|
113
|
+
|
94
114
|
# Contributing
|
95
115
|
|
96
116
|
1. Fork my repo
|
data/Rakefile
CHANGED
@@ -1,6 +1,29 @@
|
|
1
1
|
require 'rake/testtask'
|
2
|
+
require 'rdoc/task'
|
3
|
+
#$:.push File.expand_path("../tools", __FILE__)
|
2
4
|
|
3
5
|
Rake::TestTask.new do |t|
|
4
6
|
t.libs << "test"
|
5
7
|
t.test_files = FileList['test/**/*_test.rb']
|
6
8
|
end
|
9
|
+
|
10
|
+
RDoc::Task.new do |rdoc|
|
11
|
+
rdoc.rdoc_files.include("README.rdoc", "lib")
|
12
|
+
rdoc.main = "README.rdoc"
|
13
|
+
rdoc.title = "WaveFile Gem - Read/Write *.wav Files with Ruby"
|
14
|
+
rdoc.markup = "tomdoc"
|
15
|
+
rdoc.rdoc_dir = "doc"
|
16
|
+
end
|
17
|
+
|
18
|
+
namespace :test do
|
19
|
+
task :create_fixtures do
|
20
|
+
["valid", "invalid", "unsupported"].each do |subfolder|
|
21
|
+
fixtures = Dir.glob("tools/#{subfolder}/*.yml")
|
22
|
+
|
23
|
+
fixtures.each do |fixture|
|
24
|
+
basename = File.basename(fixture, ".yml")
|
25
|
+
`ruby tools/fixture_writer.rb #{fixture} test/fixtures/#{subfolder}/#{basename}.wav`
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/wavefile.rb
CHANGED
@@ -7,10 +7,12 @@ require 'wavefile/unvalidated_format'
|
|
7
7
|
require 'wavefile/writer'
|
8
8
|
|
9
9
|
module WaveFile
|
10
|
-
VERSION = "0.
|
10
|
+
VERSION = "0.8.0"
|
11
11
|
|
12
12
|
WAVEFILE_FORMAT_CODE = "WAVE" # :nodoc:
|
13
|
-
FORMAT_CODES = {:pcm => 1, :float => 3} # :nodoc:
|
13
|
+
FORMAT_CODES = {:pcm => 1, :float => 3, :extensible => 65534} # :nodoc:
|
14
|
+
SUB_FORMAT_GUID_PCM = "\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71".force_encoding("ASCII-8BIT") # :nodoc:
|
15
|
+
SUB_FORMAT_GUID_FLOAT = "\x03\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71".force_encoding("ASCII-8BIT") # :nodoc:
|
14
16
|
CHUNK_IDS = {:riff => "RIFF",
|
15
17
|
:format => "fmt ",
|
16
18
|
:data => "data",
|
data/lib/wavefile/buffer.rb
CHANGED
@@ -1,39 +1,52 @@
|
|
1
1
|
module WaveFile
|
2
|
-
# Error that is raised when an attempt is made to perform an unsupported or
|
3
|
-
# conversion between two sample data formats. For example, converting a Buffer
|
4
|
-
# 3 channels into a Buffer with 2 channels is undefined.
|
2
|
+
# Public: Error that is raised when an attempt is made to perform an unsupported or
|
3
|
+
# undefined conversion between two sample data formats. For example, converting a Buffer
|
4
|
+
# with 3 channels into a Buffer with 2 channels is undefined.
|
5
5
|
class BufferConversionError < StandardError; end
|
6
6
|
|
7
7
|
|
8
|
-
# Represents a collection of samples in a certain format (e.g. 16-bit mono).
|
9
|
-
# Reader returns sample data contained in Buffers, and Writer expects incoming sample
|
8
|
+
# Public: Represents a collection of samples in a certain format (e.g. 16-bit mono).
|
9
|
+
# Reader returns sample data contained in Buffers, and Writer expects incoming sample
|
10
10
|
# data to be contained in a Buffer as well.
|
11
11
|
#
|
12
12
|
# Contains methods to convert the sample data in the buffer to a different format.
|
13
13
|
class Buffer
|
14
14
|
|
15
|
-
# Creates a new Buffer.
|
15
|
+
# Public: Creates a new Buffer.
|
16
16
|
#
|
17
|
-
# samples - An array of samples. If the Format has 1 channel (i.e. is mono), this
|
18
|
-
# should be a flat array of samples such as [0.5, 0.4, -0.3, ...]. If the
|
19
|
-
# Format has 2 or more channels the array should include a sub-array for
|
20
|
-
# each sample frame. For example, [[0.5, 0.2], [0.1, 0.6], [-0.2, 0.4], ...]
|
17
|
+
# samples - An array of samples. If the Format has 1 channel (i.e. is mono), this
|
18
|
+
# should be a flat array of samples such as [0.5, 0.4, -0.3, ...]. If the
|
19
|
+
# Format has 2 or more channels the array should include a sub-array for
|
20
|
+
# each sample frame. For example, [[0.5, 0.2], [0.1, 0.6], [-0.2, 0.4], ...]
|
21
21
|
# for a stereo file.
|
22
22
|
#
|
23
|
-
#
|
23
|
+
# The individual samples should match the given format:
|
24
24
|
#
|
25
|
-
#
|
25
|
+
# :pcm_8 - Integer between 0 and 255
|
26
|
+
# :pcm_16 - Integer between -32_768 and 32_767
|
27
|
+
# :pcm_24 - Integer between -8_388_608 and 8_388_607
|
28
|
+
# :pcm_32 - Integer between 2_147_483_648 and 2_147_483_647
|
29
|
+
# :float - Float between -1.0 and 1.0
|
30
|
+
# :float_32 - Float between -1.0 and 1.0
|
31
|
+
# :float_64 - Float between -1.0 and 1.0
|
32
|
+
#
|
33
|
+
# format - A Format instance which describes the sample format of the sample array.
|
34
|
+
#
|
35
|
+
# Note that the sample array is not compared with the format to make sure
|
26
36
|
# they match - you are on the honor system to make sure they do. If they
|
27
37
|
# don't match, unexpected things will happen.
|
28
38
|
#
|
29
39
|
# Examples
|
30
40
|
#
|
31
|
-
# samples = ([0.5] * 50) + ([-0.5] * 50) # A 440Hz mono square wave
|
41
|
+
# samples = ([0.5] * 50) + ([-0.5] * 50) # A floating point 440Hz mono square wave
|
32
42
|
# buffer = Buffer.new(samples, Format.new(:mono, :float, 44100)
|
33
43
|
#
|
34
44
|
# samples = ([0.5, 0.5] * 50) + ([-0.5, -0.5] * 50) # A 440Hz stereo square wave
|
35
45
|
# buffer = Buffer.new(samples, Format.new(2, :float, 44100)
|
36
46
|
#
|
47
|
+
# samples = ([16000] * 50) + ([-16000] * 50) # A 16-bit PCM 440Hz mono square wave
|
48
|
+
# buffer = Buffer.new(samples, Format.new(1, :pcm_16, 44100)
|
49
|
+
#
|
37
50
|
# Returns a constructed Buffer.
|
38
51
|
def initialize(samples, format)
|
39
52
|
@samples = samples
|
@@ -41,8 +54,8 @@ module WaveFile
|
|
41
54
|
end
|
42
55
|
|
43
56
|
|
44
|
-
# Creates a new Buffer containing the sample data of this Buffer, but converted
|
45
|
-
# a different format.
|
57
|
+
# Public: Creates a new Buffer containing the sample data of this Buffer, but converted
|
58
|
+
# to a different format.
|
46
59
|
#
|
47
60
|
# new_format - The format that the sample data should be converted to
|
48
61
|
#
|
@@ -52,14 +65,15 @@ module WaveFile
|
|
52
65
|
# new_buffer = old_buffer.convert(new_format)
|
53
66
|
#
|
54
67
|
# Returns a new Buffer; the existing Buffer is unmodified.
|
68
|
+
# Raises BufferConversionError if the Buffer can't be converted to the given format
|
55
69
|
def convert(new_format)
|
56
70
|
new_samples = convert_buffer(@samples.dup, @format, new_format)
|
57
71
|
Buffer.new(new_samples, new_format)
|
58
72
|
end
|
59
73
|
|
60
74
|
|
61
|
-
# Converts the sample data contained in the Buffer to a new format. The sample
|
62
|
-
# is converted in place, so the existing Buffer is modified.
|
75
|
+
# Public: Converts the sample data contained in the Buffer to a new format. The sample
|
76
|
+
# data is converted in place, so the existing Buffer is modified.
|
63
77
|
#
|
64
78
|
# new_format - The format that the sample data should be converted to
|
65
79
|
#
|
@@ -69,6 +83,7 @@ module WaveFile
|
|
69
83
|
# old_buffer.convert!(new_format)
|
70
84
|
#
|
71
85
|
# Returns self.
|
86
|
+
# Raises BufferConversionError if the Buffer can't be converted to the given format
|
72
87
|
def convert!(new_format)
|
73
88
|
@samples = convert_buffer(@samples, @format, new_format)
|
74
89
|
@format = new_format
|
@@ -76,26 +91,26 @@ module WaveFile
|
|
76
91
|
end
|
77
92
|
|
78
93
|
|
79
|
-
# Returns the number of channels the buffer's sample data has
|
94
|
+
# Public: Returns the number of channels the buffer's sample data has
|
80
95
|
def channels
|
81
96
|
@format.channels
|
82
97
|
end
|
83
98
|
|
84
99
|
|
85
|
-
# Returns the bits per sample of the buffer's sample data
|
100
|
+
# Public: Returns the bits per sample of the buffer's sample data
|
86
101
|
def bits_per_sample
|
87
102
|
@format.bits_per_sample
|
88
103
|
end
|
89
104
|
|
90
105
|
|
91
|
-
# Returns the sample rate of the buffer's sample data
|
106
|
+
# Public: Returns the sample rate of the buffer's sample data
|
92
107
|
def sample_rate
|
93
108
|
@format.sample_rate
|
94
109
|
end
|
95
110
|
|
96
|
-
# Returns the sample data contained in the Buffer as an Array. If the Format
|
97
|
-
# 1 channel, the Array will be a flat list of samples. If the Format has 2 or
|
98
|
-
# channels, the Array will include sub arrays for each sample frame, with a sample
|
111
|
+
# Public: Returns the sample data contained in the Buffer as an Array. If the Format
|
112
|
+
# has 1 channel, the Array will be a flat list of samples. If the Format has 2 or
|
113
|
+
# more channels, the Array will include sub arrays for each sample frame, with a sample
|
99
114
|
# for each channel.
|
100
115
|
#
|
101
116
|
# Examples
|
@@ -207,10 +222,10 @@ module WaveFile
|
|
207
222
|
|
208
223
|
if more_than_one_channel
|
209
224
|
samples.map! do |sample|
|
210
|
-
sample.map!
|
225
|
+
sample.map!(&converter)
|
211
226
|
end
|
212
227
|
else
|
213
|
-
samples.map!
|
228
|
+
samples.map!(&converter)
|
214
229
|
end
|
215
230
|
end
|
216
231
|
end
|
@@ -1,6 +1,12 @@
|
|
1
|
-
require 'wavefile/chunk_readers/
|
1
|
+
require 'wavefile/chunk_readers/riff_reader'
|
2
|
+
require 'wavefile/chunk_readers/base_chunk_reader'
|
3
|
+
require 'wavefile/chunk_readers/generic_chunk_reader'
|
4
|
+
require 'wavefile/chunk_readers/riff_chunk_reader'
|
5
|
+
require 'wavefile/chunk_readers/format_chunk_reader'
|
6
|
+
require 'wavefile/chunk_readers/data_chunk_reader'
|
2
7
|
|
3
8
|
module WaveFile
|
9
|
+
# Internal
|
4
10
|
module ChunkReaders # :nodoc:
|
5
11
|
end
|
6
12
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module WaveFile
|
2
|
+
module ChunkReaders
|
3
|
+
# Internal
|
4
|
+
class DataChunkReader < BaseChunkReader # :nodoc:
|
5
|
+
def initialize(io, chunk_size, raw_native_format, format=nil)
|
6
|
+
@io = io
|
7
|
+
@raw_native_format = raw_native_format
|
8
|
+
|
9
|
+
@total_sample_frames = chunk_size / @raw_native_format.block_align
|
10
|
+
@current_sample_frame = 0
|
11
|
+
|
12
|
+
@readable_format = true
|
13
|
+
begin
|
14
|
+
@native_format = @raw_native_format.to_validated_format
|
15
|
+
@pack_code = PACK_CODES[@native_format.sample_format][@native_format.bits_per_sample]
|
16
|
+
rescue FormatError
|
17
|
+
@readable_format = false
|
18
|
+
@native_format = nil
|
19
|
+
@pack_code = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
@format = (format == nil) ? (@native_format || @raw_native_format) : format
|
23
|
+
end
|
24
|
+
|
25
|
+
def read(sample_frame_count)
|
26
|
+
raise UnsupportedFormatError unless @readable_format
|
27
|
+
|
28
|
+
if @current_sample_frame >= @total_sample_frames
|
29
|
+
#FIXME: Do something different here, because the end of the file has not actually necessarily been reached
|
30
|
+
raise EOFError
|
31
|
+
elsif sample_frame_count > sample_frames_remaining
|
32
|
+
sample_frame_count = sample_frames_remaining
|
33
|
+
end
|
34
|
+
|
35
|
+
samples = @io.sysread(sample_frame_count * @native_format.block_align).unpack(@pack_code)
|
36
|
+
@current_sample_frame += sample_frame_count
|
37
|
+
|
38
|
+
if @native_format.bits_per_sample == 24
|
39
|
+
samples = convert_24_bit_samples(samples)
|
40
|
+
end
|
41
|
+
|
42
|
+
if @native_format.channels > 1
|
43
|
+
samples = samples.each_slice(@native_format.channels).to_a
|
44
|
+
end
|
45
|
+
|
46
|
+
buffer = Buffer.new(samples, @native_format)
|
47
|
+
buffer.convert(@format)
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :raw_native_format,
|
51
|
+
:format,
|
52
|
+
:current_sample_frame,
|
53
|
+
:total_sample_frames,
|
54
|
+
:readable_format
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# The number of sample frames in the file after the current sample frame
|
59
|
+
def sample_frames_remaining
|
60
|
+
@total_sample_frames - @current_sample_frame
|
61
|
+
end
|
62
|
+
|
63
|
+
# Since Ruby doesn't have a way to natively extract 24-bit values using pack/unpack,
|
64
|
+
# unsigned bytes are read instead, and then every 3 is manually combined into a
|
65
|
+
# signed 24-bit integer.
|
66
|
+
# Since the sample data is little endian, the 3 bytes will go from least->most significant
|
67
|
+
def convert_24_bit_samples(samples)
|
68
|
+
samples.each_slice(3).map do |least_significant_byte, middle_byte, most_significant_byte|
|
69
|
+
# Convert the most significant byte from unsigned to signed, since 24-bit samples are signed
|
70
|
+
most_significant_byte = [most_significant_byte].pack("c").unpack("c").first
|
71
|
+
|
72
|
+
(most_significant_byte << 16) | (middle_byte << 8) | least_significant_byte
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|