jstrait-wavefile 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. data/README +17 -1
  2. data/lib/wavefile.rb +110 -17
  3. metadata +1 -1
data/README CHANGED
@@ -6,13 +6,21 @@ First, install the WaveFile gem...
6
6
 
7
7
  ...and include it in your Ruby program:
8
8
 
9
- require 'WaveFile'
9
+ require 'wavefile'
10
10
 
11
11
  To open a wave file and get the raw sample data:
12
12
 
13
13
  w = WaveFile.open("myfile.wav")
14
14
  samples = w.sample_data
15
15
 
16
+ Sample data is stored in an array. For mono files, each sample is a single number. For stereo files, each sample is represented by an array containing a value for the left and right channel.
17
+
18
+ # Mono example
19
+ [0, 128, 255, 128]
20
+
21
+ # Stereo example
22
+ [[0, 255], [128, 128], [255, 0], [128, 128]]
23
+
16
24
  You can also get the sample data in a normalized form, with each sample between -1.0 and 1.0:
17
25
 
18
26
  normalized_samples = w.normalized_sample_data
@@ -20,6 +28,8 @@ You can also get the sample data in a normalized form, with each sample between
20
28
  You can get basic metadata:
21
29
 
22
30
  w.num_channels # 1 for mono, 2 for stereo
31
+ w.mono? # Alias for num_channels == 1
32
+ w.stereo? # Alias for num_channels == 2
23
33
  w.sample_rate # 11025, 22050, 44100, etc.
24
34
  w.bits_per_sample # 8 or 16
25
35
 
@@ -32,3 +42,9 @@ To create and save a new wave file:
32
42
  w.save("myfile.wav")
33
43
 
34
44
  When calling the sample_data=() method, the passed in array can contain either raw samples or normalized samples. If the first item in the array is a Float, the entire array is assumed to be normalized. Normalized samples are automatically converted into raw samples when saving.
45
+
46
+ You can reverse a file with the reverse() method:
47
+
48
+ w = WaveFile.open("myfile.wav")
49
+ w.reverse()
50
+ w.save("myfile_reversed.wav")
data/lib/wavefile.rb CHANGED
@@ -55,13 +55,19 @@ class WaveFile
55
55
  HEADER_SIZE = 36
56
56
 
57
57
  def initialize(num_channels, sample_rate, bits_per_sample, sample_data = [])
58
- @num_channels = num_channels
58
+ if num_channels == :mono
59
+ @num_channels = 1
60
+ elsif num_channels == :stereo
61
+ @num_channels = 2
62
+ else
63
+ @num_channels = num_channels
64
+ end
59
65
  @sample_rate = sample_rate
60
66
  @bits_per_sample = bits_per_sample
61
67
  @sample_data = sample_data
62
68
 
63
- @byte_rate = sample_rate * num_channels * (bits_per_sample / 8)
64
- @block_align = num_channels * (bits_per_sample / 8)
69
+ @byte_rate = sample_rate * @num_channels * (bits_per_sample / 8)
70
+ @block_align = @num_channels * (bits_per_sample / 8)
65
71
  end
66
72
 
67
73
  def self.open(path)
@@ -70,6 +76,7 @@ class WaveFile
70
76
  if valid_header?(header)
71
77
  sample_data = read_sample_data(path,
72
78
  header[:sub_chunk1_size],
79
+ header[:num_channels],
73
80
  header[:bits_per_sample],
74
81
  header[:sub_chunk2_size])
75
82
 
@@ -78,7 +85,7 @@ class WaveFile
78
85
  header[:bits_per_sample],
79
86
  sample_data)
80
87
  else
81
- raise StandardError, "#{path} is not a valid wave file"
88
+ raise StandardError, "#{path} is either not a valid wave file, or is in an unsupported format"
82
89
  end
83
90
 
84
91
  return wave_file
@@ -105,10 +112,21 @@ class WaveFile
105
112
  file_contents += [sample_data_size].pack("V")
106
113
 
107
114
  # Write the sample data
115
+ if !mono?
116
+ output_sample_data = []
117
+ @sample_data.each{|sample|
118
+ sample.each{|sub_sample|
119
+ output_sample_data << sub_sample
120
+ }
121
+ }
122
+ else
123
+ output_sample_data = @sample_data
124
+ end
125
+
108
126
  if @bits_per_sample == 8
109
- file_contents += @sample_data.pack("C*")
127
+ file_contents += output_sample_data.pack("C*")
110
128
  elsif @bits_per_sample == 16
111
- file_contents += @sample_data.pack("s*")
129
+ file_contents += output_sample_data.pack("s*")
112
130
  else
113
131
  raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
114
132
  end
@@ -124,34 +142,97 @@ class WaveFile
124
142
 
125
143
  def normalized_sample_data()
126
144
  if @bits_per_sample == 8
127
- normalized_sample_data = @sample_data.map {|sample| (sample.to_f / 511.0) * 2.0 }
145
+ min_value = 128.0
146
+ max_value = 127.0
147
+ midpoint = 128
128
148
  elsif @bits_per_sample == 16
129
- normalized_sample_data = @sample_data.map {|sample| (sample.to_f / 32767.0) }
149
+ min_value = 32768.0
150
+ max_value = 32767.0
151
+ midpoint = 0
130
152
  else
131
153
  raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
132
154
  end
133
155
 
156
+ if mono?
157
+ normalized_sample_data = @sample_data.map {|sample|
158
+ sample -= midpoint
159
+ if sample < 0
160
+ sample.to_f / min_value
161
+ else
162
+ sample.to_f / max_value
163
+ end
164
+ }
165
+ else
166
+ normalized_sample_data = @sample_data.map {|sample|
167
+ sample.map {|sub_sample|
168
+ sub_sample -= midpoint
169
+ if sub_sample < 0
170
+ sub_sample.to_f / min_value
171
+ else
172
+ sub_sample.to_f / max_value
173
+ end
174
+ }
175
+ }
176
+ end
177
+
134
178
  return normalized_sample_data
135
179
  end
136
180
 
137
181
  def sample_data=(sample_data)
138
- if sample_data.length > 0 && sample_data[0].class == Float
182
+ if sample_data.length > 0 && ((mono? && sample_data[0].class == Float) ||
183
+ (!mono? && sample_data[0][0].class == Float))
139
184
  if @bits_per_sample == 8
140
185
  # Samples in 8-bit wave files are stored as a unsigned byte
141
- # Effective values are 0 to 255
142
- @sample_data = sample_data.map {|sample| ((sample * 127.0).to_i) + 127 }
186
+ # Effective values are 0 to 255, midpoint at 128
187
+ min_value = 128.0
188
+ max_value = 127.0
189
+ midpoint = 128
143
190
  elsif @bits_per_sample == 16
144
191
  # Samples in 16-bit wave files are stored as a signed little-endian short
145
- # Effective values are -32768 to 32767
146
- @sample_data = sample_data.map {|sample| (sample * 32767.0).to_i }
192
+ # Effective values are -32768 to 32767, midpoint at 0
193
+ min_value = 32768.0
194
+ max_value = 32767.0
195
+ midpoint = 0
147
196
  else
148
197
  raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
149
198
  end
199
+
200
+ if mono?
201
+ @sample_data = sample_data.map {|sample|
202
+ if(sample < 0.0)
203
+ (sample * min_value).round + midpoint
204
+ else
205
+ (sample * max_value).round + midpoint
206
+ end
207
+ }
208
+ else
209
+ @sample_data = sample_data.map {|sample|
210
+ sample.map {|sub_sample|
211
+ if(sub_sample < 0.0)
212
+ (sub_sample * min_value).round + midpoint
213
+ else
214
+ (sub_sample * max_value).round + midpoint
215
+ end
216
+ }
217
+ }
218
+ end
150
219
  else
151
220
  @sample_data = sample_data
152
221
  end
153
222
  end
154
223
 
224
+ def mono?()
225
+ return num_channels == 1
226
+ end
227
+
228
+ def stereo?()
229
+ return num_channels == 2
230
+ end
231
+
232
+ def reverse()
233
+ sample_data.reverse!()
234
+ end
235
+
155
236
  attr_reader :num_channels, :sample_rate, :bits_per_sample, :byte_rate, :block_align
156
237
 
157
238
  private
@@ -192,8 +273,8 @@ private
192
273
  def self.valid_header?(header)
193
274
  valid_bits_per_sample = header[:bits_per_sample] == 8 ||
194
275
  header[:bits_per_sample] == 16
195
-
196
- valid_num_channels = header[:num_channels] == 1 # Only mono files supported for now
276
+
277
+ valid_num_channels = (1..65535) === header[:num_channels]
197
278
 
198
279
  return valid_bits_per_sample &&
199
280
  valid_num_channels &&
@@ -204,7 +285,7 @@ private
204
285
  header[:sub_chunk2_id] == SUB_CHUNK2_ID
205
286
  end
206
287
 
207
- def self.read_sample_data(path, sub_chunk1_size, bits_per_sample, sample_data_size)
288
+ def self.read_sample_data(path, sub_chunk1_size, num_channels, bits_per_sample, sample_data_size)
208
289
  offset = 20 + sub_chunk1_size + 8
209
290
  file = File.open(path, "rb")
210
291
 
@@ -217,7 +298,19 @@ private
217
298
  data = file.sysread(sample_data_size).unpack("s*")
218
299
  else
219
300
  data = []
220
- end
301
+ end
302
+
303
+ if(num_channels > 1)
304
+ multichannel_data = []
305
+
306
+ i = 0
307
+ while i < data.length
308
+ multichannel_data << data[i..(num_channels + i)]
309
+ i += num_channels
310
+ end
311
+
312
+ data = stereo_data
313
+ end
221
314
  rescue EOFError
222
315
  file.close()
223
316
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jstrait-wavefile
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Strait