wavefile 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
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=
2
+ SHA1:
3
+ metadata.gz: 48ad3ca7155a04fd1b674dd9add8f556746db460
4
+ data.tar.gz: a8f70d7c7ddce88d1bd93a7c2fef2963b80fc2d4
5
+ SHA512:
6
+ metadata.gz: 3eb29c14f9146190fa8a01bb36e4a1e25259f09bf36d27f390bb99e1229f25abb94dd9438937fcb52f856d088b114717a626b0e78948a0156551cb313a02a2b6
7
+ data.tar.gz: 91d5abb8d5284a387b267ef951a5974298ceb497bb7b6cb8c7e53c034a32ac5b90c3a2f2e356cfabf788c0f279427fbdf877e5e052f42ebfc20f461de45558d4
data/README.markdown CHANGED
@@ -2,6 +2,7 @@ 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
+ For more info, check out the website: <http://wavefilegem.com/>
5
6
 
6
7
  # Example Usage
7
8
 
@@ -27,7 +28,7 @@ More examples can be [found on the wiki](https://github.com/jstrait/wavefile/wik
27
28
  # Features
28
29
 
29
30
  * 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
+ * PCM (8, 16, 24, and 32 bits per sample)
31
32
  * Floating Point (32 and 64 bits per sample)
32
33
  * Ability to read sample data from a file in any of the supported formats, regardless of the file's actual sample format
33
34
 
@@ -46,24 +47,14 @@ More examples can be [found on the wiki](https://github.com/jstrait/wavefile/wik
46
47
  * Pure Ruby, so no need to compile a separate extension in order to use it.
47
48
 
48
49
 
49
- # Current Release: v0.5.0
50
+ # Current Release: v0.6.0
50
51
 
51
52
  This release includes these improvements:
52
53
 
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).
54
+ * Support for reading and writing Wave files containing 24-bit PCM sample data, and the ability to convert buffers containing 24-bit PCM sample data to/from other formats. (Thanks to [Rich Orton](https://github.com/richorton) for suggesting this).
55
+ * Reading files with 2 or more channels is now faster.
56
+ * Converting buffers from one format to another is now faster in certain cases.
57
+ * Bug fix: Files containing certain chunks with an odd size are now read properly. According to the Wave file spec, all chunks should be aligned to an even number of bytes. If the chunk has an odd size, a padding byte should be appended to bring the chunk to an even size. The `Reader` class now properly takes this expected padding byte into account for all chunks when reading files. (Previously it just took this into account for the main `data` chunk). (Thanks to [Andrew Kuklewicz](https://github.com/kookster) for reporting this).
67
58
 
68
59
 
69
60
  # Compatibility
@@ -71,7 +62,7 @@ This release also includes changes that are not backwards compatible with v0.4.0
71
62
  WaveFile has been tested with these Ruby versions, and appears to be compatible with them:
72
63
 
73
64
  * MRI 2.0.0, 1.9.3, 1.9.2, 1.9.1, 1.8.7
74
- * JRuby 1.7.3
65
+ * JRuby 1.7.8
75
66
  * Rubinius 1.2.4
76
67
  * MacRuby 0.12
77
68
 
data/lib/wavefile.rb CHANGED
@@ -6,11 +6,11 @@ require 'wavefile/reader'
6
6
  require 'wavefile/writer'
7
7
 
8
8
  module WaveFile
9
- VERSION = "0.5.0"
9
+ VERSION = "0.6.0"
10
10
 
11
- WAVEFILE_FORMAT_CODE = "WAVE"
12
- FORMAT_CHUNK_BYTE_LENGTH = {:pcm => 16, :float => 18}
13
- FORMAT_CODES = {:pcm => 1, :float => 3}
11
+ WAVEFILE_FORMAT_CODE = "WAVE" # :nodoc:
12
+ FORMAT_CHUNK_BYTE_LENGTH = {:pcm => 16, :float => 18} # :nodoc:
13
+ FORMAT_CODES = {:pcm => 1, :float => 3} # :nodoc:
14
14
  CHUNK_IDS = {:riff => "RIFF",
15
15
  :format => "fmt ",
16
16
  :data => "data",
@@ -23,12 +23,12 @@ module WaveFile
23
23
  :labeled_text => "ltxt",
24
24
  :note => "note",
25
25
  :sample => "smpl",
26
- :instrument => "inst" }
26
+ :instrument => "inst" } # :nodoc:
27
27
 
28
- PACK_CODES = {:pcm => {8 => "C*", 16 => "s*", 32 => "l*"},
29
- :float => { 32 => "e*", 64 => "E*"}}
28
+ PACK_CODES = {:pcm => {8 => "C*", 16 => "s*", 24 => "C*", 32 => "l*"},
29
+ :float => { 32 => "e*", 64 => "E*"}} # :nodoc:
30
30
 
31
- UNSIGNED_INT_16 = "v"
32
- UNSIGNED_INT_32 = "V"
31
+ UNSIGNED_INT_16 = "v" # :nodoc:
32
+ UNSIGNED_INT_32 = "V" # :nodoc:
33
33
  end
34
34
 
@@ -1,32 +1,54 @@
1
1
  module WaveFile
2
- # Error that is raised when an attempt is made to perform an unsupported or undefined
3
- # conversion between two sample data formats.
2
+ # Error that is raised when an attempt is made to perform an unsupported or undefined
3
+ # conversion between two sample data formats. For example, converting a Buffer with
4
+ # 3 channels into a Buffer with 2 channels is undefined.
4
5
  class BufferConversionError < StandardError; end
5
6
 
6
7
 
7
- # Represents a collection of samples in a certain format (e.g. 16-bit mono).
8
- # Reader returns sample data contained in Buffers, and Writer expects incoming sample
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
9
10
  # data to be contained in a Buffer as well.
10
11
  #
11
12
  # Contains methods to convert the sample data in the buffer to a different format.
12
13
  class Buffer
13
14
 
14
- # Creates a new Buffer. You are on the honor system to make sure that the given
15
- # sample data matches the given format.
15
+ # Creates a new Buffer.
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], ...]
21
+ # for a stereo file.
22
+ #
23
+ # format - A Format instance which describes the sample format of the sample array.
24
+ #
25
+ # Note that the sample array is not compared with the format to make sure
26
+ # they match - you are on the honor system to make sure they do. If they
27
+ # don't match, unexpected things will happen.
28
+ #
29
+ # Examples
30
+ #
31
+ # samples = ([0.5] * 50) + ([-0.5] * 50) # A 440Hz mono square wave
32
+ # buffer = Buffer.new(samples, Format.new(:mono, :float, 44100)
33
+ #
34
+ # samples = ([0.5, 0.5] * 50) + ([-0.5, -0.5] * 50) # A 440Hz stereo square wave
35
+ # buffer = Buffer.new(samples, Format.new(2, :float, 44100)
36
+ #
37
+ # Returns a constructed Buffer.
16
38
  def initialize(samples, format)
17
39
  @samples = samples
18
40
  @format = format
19
41
  end
20
42
 
21
43
 
22
- # Creates a new Buffer containing the sample data of this Buffer, but converted to
44
+ # Creates a new Buffer containing the sample data of this Buffer, but converted to
23
45
  # a different format.
24
46
  #
25
47
  # new_format - The format that the sample data should be converted to
26
48
  #
27
49
  # Examples
28
50
  #
29
- # new_format = Format.new(:mono, 16, 44100)
51
+ # new_format = Format.new(:mono, :pcm_16, 44100)
30
52
  # new_buffer = old_buffer.convert(new_format)
31
53
  #
32
54
  # Returns a new Buffer; the existing Buffer is unmodified.
@@ -36,14 +58,14 @@ module WaveFile
36
58
  end
37
59
 
38
60
 
39
- # Converts the sample data contained in the Buffer to a new format. The sample data
61
+ # Converts the sample data contained in the Buffer to a new format. The sample data
40
62
  # is converted in place, so the existing Buffer is modified.
41
63
  #
42
64
  # new_format - The format that the sample data should be converted to
43
65
  #
44
66
  # Examples
45
67
  #
46
- # new_format = Format.new(:mono, 16, 44100)
68
+ # new_format = Format.new(:mono, :pcm_16, 44100)
47
69
  # old_buffer.convert!(new_format)
48
70
  #
49
71
  # Returns self.
@@ -54,30 +76,50 @@ module WaveFile
54
76
  end
55
77
 
56
78
 
57
- # The number of channels the buffer's sample data has
79
+ # Returns the number of channels the buffer's sample data has
58
80
  def channels
59
81
  @format.channels
60
82
  end
61
83
 
62
84
 
63
- # The bits per sample of the buffer's sample data
85
+ # Returns the bits per sample of the buffer's sample data
64
86
  def bits_per_sample
65
87
  @format.bits_per_sample
66
88
  end
67
89
 
68
90
 
69
- # The sample rate of the buffer's sample data
91
+ # Returns the sample rate of the buffer's sample data
70
92
  def sample_rate
71
93
  @format.sample_rate
72
94
  end
73
95
 
96
+ # Returns the sample data contained in the Buffer as an Array. If the Format has
97
+ # 1 channel, the Array will be a flat list of samples. If the Format has 2 or more
98
+ # channels, the Array will include sub arrays for each sample frame, with a sample
99
+ # for each channel.
100
+ #
101
+ # Examples
102
+ #
103
+ # samples = mono_buffer.samples
104
+ # # => [-0.5, 0.3, 0.2, -0.9, ...]
105
+ #
106
+ # samples = stereo_buffer.samples
107
+ # # => [[-0.2, 0.5], [0.1, 0.2], [-0.4, 0.7], [0.1, 0.2], ...]
108
+ #
109
+ # samples = three_channel_buffer.samples
110
+ # # => [[0.3, 0.5, 0.2], [-0.1, 0.2, -0.9], [0.2, 0.3, -0.4], [0.1, 0.2, -0.8], ...]
74
111
  attr_reader :samples
75
112
 
76
113
  private
77
114
 
78
115
  def convert_buffer(samples, old_format, new_format)
79
- samples = convert_channels(samples, old_format.channels, new_format.channels)
80
- samples = convert_sample_format(samples, old_format, new_format)
116
+ if old_format.channels > new_format.channels
117
+ samples = convert_channels(samples, old_format.channels, new_format.channels)
118
+ samples = convert_sample_format(samples, old_format, new_format)
119
+ else
120
+ samples = convert_sample_format(samples, old_format, new_format)
121
+ samples = convert_channels(samples, old_format.channels, new_format.channels)
122
+ end
81
123
 
82
124
  samples
83
125
  end
@@ -141,6 +183,8 @@ module WaveFile
141
183
  convert_sample_format_helper(samples) {|sample| (sample - 128).to_f / 128.0 }
142
184
  elsif old_bits_per_sample == 16
143
185
  convert_sample_format_helper(samples) {|sample| sample.to_f / 32768.0 }
186
+ elsif old_bits_per_sample == 24
187
+ convert_sample_format_helper(samples) {|sample| sample.to_f / 8388608.0 }
144
188
  elsif old_bits_per_sample == 32
145
189
  convert_sample_format_helper(samples) {|sample| sample.to_f / 2147483648.0 }
146
190
  end
@@ -151,6 +195,8 @@ module WaveFile
151
195
  convert_sample_format_helper(samples) {|sample| (sample * 127.0).round + 128 }
152
196
  elsif new_bits_per_sample == 16
153
197
  convert_sample_format_helper(samples) {|sample| (sample * 32767.0).round }
198
+ elsif new_bits_per_sample == 24
199
+ convert_sample_format_helper(samples) {|sample| (sample * 8388607.0).round }
154
200
  elsif new_bits_per_sample == 32
155
201
  convert_sample_format_helper(samples) {|sample| (sample * 2147483647.0).round }
156
202
  end
@@ -1,6 +1,32 @@
1
1
  module WaveFile
2
- # Calculates playback time given the number of sample frames and the sample rate.
2
+ # Calculates playback time given the number of sample frames and the sample rate. For
3
+ # example, you can use this to calculate how long a given Wave file is.
4
+ #
5
+ # The hours, minutes, seconds, and milliseconds fields return values like you would
6
+ # see on a stopwatch, and not the total amount of time in that unit. For example, a
7
+ # stopwatch running for exactly 2 hours would show something like "2:00:00.000".
8
+ # Accordingly, if the given sample frame count and sample rate add up to exactly
9
+ # 2 hours, then hours will be 2, and minutes, seconds, and milliseconds will all be 0.
10
+ #
11
+ # This class is immutable - once a new Duration is constructed, it can't be modified.
3
12
  class Duration
13
+ # Constructs a new immutable Duration.
14
+ #
15
+ # sample_frame_count - The number of sample frames, i.e. the number
16
+ # samples in each channel.
17
+ # sample_rate - The number of samples per second, such as 44100
18
+ #
19
+ # Examples:
20
+ #
21
+ # duration = Duration.new(400_000_000, 44100)
22
+ # duration.hours # => 2
23
+ # duration.minutes # => 31
24
+ # duration.seconds # => 10
25
+ # duration.milliseconds # => 294
26
+ #
27
+ # Note that the hours, minutes, seconds, and milliseconds fields do not return
28
+ # the total of the respective unit in the entire duration. For example, if a
29
+ # duration is exactly 2 hours, then minutes will be 0, not 120.
4
30
  def initialize(sample_frame_count, sample_rate)
5
31
  @sample_frame_count = sample_frame_count
6
32
  @sample_rate = sample_rate
@@ -28,7 +54,12 @@ module WaveFile
28
54
 
29
55
  @milliseconds = (sample_frame_count / sample_frames_per_millisecond).floor
30
56
  end
31
-
32
- attr_reader :sample_frame_count, :sample_rate, :hours, :minutes, :seconds, :milliseconds
57
+
58
+ attr_reader :sample_frame_count
59
+ attr_reader :sample_rate
60
+ attr_reader :hours
61
+ attr_reader :minutes
62
+ attr_reader :seconds
63
+ attr_reader :milliseconds
33
64
  end
34
65
  end
@@ -1,20 +1,44 @@
1
1
  module WaveFile
2
2
  class InvalidFormatError < StandardError; end
3
3
 
4
+ # Represents information about the data format for a Wave file, such as number of
5
+ # channels, bits per sample, sample rate, and so forth. A Format instance is used
6
+ # by Reader to indicate what format to read samples out as, and by Writer to
7
+ # indicate what format to write samples as.
8
+ #
9
+ # This class is immutable - once a new Format is constructed, it can't be modified.
4
10
  class Format
5
11
  # Not using ranges because of 1.8.7 performance problems with Range.max
6
- MIN_CHANNELS = 1
7
- MAX_CHANNELS = 65535
12
+ MIN_CHANNELS = 1 # :nodoc:
13
+ MAX_CHANNELS = 65535 # :nodoc:
8
14
 
9
- MIN_SAMPLE_RATE = 1
10
- MAX_SAMPLE_RATE = 4_294_967_296
15
+ MIN_SAMPLE_RATE = 1 # :nodoc:
16
+ MAX_SAMPLE_RATE = 4_294_967_296 # :nodoc:
11
17
 
12
- SUPPORTED_SAMPLE_FORMATS = [:pcm, :float]
18
+ SUPPORTED_SAMPLE_FORMATS = [:pcm, :float] # :nodoc:
13
19
  SUPPORTED_BITS_PER_SAMPLE = {
14
- :pcm => [8, 16, 32],
20
+ :pcm => [8, 16, 24, 32],
15
21
  :float => [32, 64],
16
- }
22
+ } # :nodoc:
17
23
 
24
+ # Constructs a new immutable Format.
25
+ #
26
+ # channels - The number of channels in the format. Can either be a Fixnum
27
+ # (e.g. 1, 2, 3) or the symbols :mono (equivalent to 1) or
28
+ # :stereo (equivalent to 2).
29
+ # format_code - A symbol indicating the format of each sample. Consists of
30
+ # two parts: a format code, and the bits per sample. The valid
31
+ # values are :pcm_8, :pcm_16, :pcm_32, :float_32, :float_64,
32
+ # and :float (equivalent to :float_32)
33
+ # sample_rate - The number of samples per second, such as 44100
34
+ #
35
+ # Examples
36
+ #
37
+ # format = Format.new(1, :pcm_16, 44100)
38
+ # format = Format.new(:mono, :pcm_16, 44100) # Equivalent to above
39
+ #
40
+ # format = Format.new(:stereo, :float_32, 44100)
41
+ # format = Format.new(:stereo, :float, 44100)
18
42
  def initialize(channels, format_code, sample_rate)
19
43
  channels = normalize_channels(channels)
20
44
  sample_format, bits_per_sample = normalize_format_code(format_code)
@@ -31,15 +55,37 @@ module WaveFile
31
55
  @byte_rate = @block_align * @sample_rate
32
56
  end
33
57
 
58
+ # Returns true if the format has 1 channel, false otherwise.
34
59
  def mono?
35
60
  @channels == 1
36
61
  end
37
62
 
63
+ # Returns true if the format has 2 channels, false otherwise.
38
64
  def stereo?
39
65
  @channels == 2
40
66
  end
41
67
 
42
- attr_reader :channels, :sample_format, :bits_per_sample, :sample_rate, :block_align, :byte_rate
68
+ # Returns the number of channels, such as 1 or 2. This will always return a
69
+ # Fixnum, even if the number of channels is specified with a symbol (e.g. :mono)
70
+ # in the constructor.
71
+ attr_reader :channels
72
+
73
+ # Returns a symbol indicating the sample format, such as :pcm or :float
74
+ attr_reader :sample_format
75
+
76
+ # Returns the number of bits per sample, such as 8, 16, 24, 32, or 64.
77
+ attr_reader :bits_per_sample
78
+
79
+ # Returns the number of samples per second, such as 44100.
80
+ attr_reader :sample_rate
81
+
82
+ # Returns the number of bytes in each sample frame. For example, in a 16-bit stereo file,
83
+ # this will be 4 (2 bytes for each 16-bit sample, times 2 channels).
84
+ attr_reader :block_align
85
+
86
+ # Returns the number of bytes contained in 1 second of sample data.
87
+ # Is equivalent to block_align * sample_rate.
88
+ attr_reader :byte_rate
43
89
 
44
90
  private
45
91
 
data/lib/wavefile/info.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  module WaveFile
2
+ # Contains metadata about an existing wave file. Returned by Reader.info.
3
+ #
4
+ # This class is immutable - once a new Info is constructed, it can't be modified.
2
5
  class Info
3
- def initialize(file_name, raw_format_chunk, sample_frame_count)
6
+ def initialize(file_name, raw_format_chunk, sample_frame_count) # :nodoc:
4
7
  @file_name = file_name
5
8
  @audio_format = raw_format_chunk[:audio_format]
6
9
  @channels = raw_format_chunk[:channels]
@@ -13,8 +16,35 @@ module WaveFile
13
16
  @duration = Duration.new(@sample_frame_count, @sample_rate)
14
17
  end
15
18
 
16
- attr_reader :file_name,
17
- :audio_format, :channels, :bits_per_sample, :sample_rate, :byte_rate, :block_align,
18
- :sample_frame_count, :duration
19
+ # Returns the name of file this Info contains metadata about.
20
+ attr_reader :file_name
21
+
22
+ # Returns a Fixnum indicating the audio format, such as 1 for PCM or 3 for IEEE float.
23
+ attr_reader :audio_format
24
+
25
+ # Returns the number of channels, such as 1 or 2.
26
+ attr_reader :channels
27
+
28
+ # Returns the number of bits per sample, such as 8, 16, 32, or 64.
29
+ attr_reader :bits_per_sample
30
+
31
+ # Returns the number of samples per second, such as 44100.
32
+ attr_reader :sample_rate
33
+
34
+ # Returns the number of bytes contained in 1 second of sample data.
35
+ # Is equivalent to block_align * sample_rate.
36
+ attr_reader :byte_rate
37
+
38
+ # Returns the number of bytes in each sample frame. For example, in a 16-bit stereo file,
39
+ # this will be 4 (2 bytes for each 16-bit sample, times 2 channels).
40
+ attr_reader :block_align
41
+
42
+ # Returns the total number of sample frames in the file. A sample frame contains a single
43
+ # sample for each channel. So if there are 1,000 sample frames in a stereo file, this means
44
+ # there are 1,000 left-channel samples and 1,000 right-channel samples.
45
+ attr_reader :sample_frame_count
46
+
47
+ # Returns a Duration instance for the total number of sample frames in the file
48
+ attr_reader :duration
19
49
  end
20
50
  end