jstrait-wavefile 0.1.0 → 0.2.0

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