nu_wav 0.3.4 → 0.4.2

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.
@@ -0,0 +1,331 @@
1
+ module NuWav
2
+ class Chunk
3
+ attr_accessor :id, :size, :raw
4
+
5
+ def self.parse(id, size, file)
6
+ raw = file.read(size)
7
+ chunk = self.new(id, size, raw)
8
+ chunk.parse
9
+ return chunk
10
+ end
11
+
12
+ def initialize(id=nil, size=nil, raw=nil)
13
+ @id, @size, @raw = id, size, raw
14
+ end
15
+
16
+ def parse
17
+ end
18
+
19
+ def read_dword(start)
20
+ @raw[start..(start+3)].unpack('V').first
21
+ end
22
+
23
+ def read_word(start)
24
+ @raw[start..(start+1)].unpack('v').first
25
+ end
26
+
27
+ def read_char(start, length=(@raw.length-start))
28
+ (@raw[start..(start+length-1)] || '').strip
29
+ end
30
+
31
+ def write_dword(val)
32
+ val ||= 0
33
+ [val].pack('V')
34
+ end
35
+
36
+ def write_word(val)
37
+ val ||= 0
38
+ [val].pack('v')
39
+ end
40
+
41
+ def write_char(val, length=nil)
42
+ val ||= ''
43
+ val = val.to_s
44
+ length ||= val.length
45
+ # NuWav::WaveFile.log "length:#{length} val.length:#{val.length} val:#{val}"
46
+ padding = "\0" * [(length - val.length), 0].max
47
+ out = val[0,length] + padding
48
+ # NuWav::WaveFile.log out
49
+ out
50
+ end
51
+
52
+ def to_binary
53
+ end
54
+ end
55
+
56
+
57
+ class RiffChunk
58
+ attr_accessor :id, :size, :riff_type
59
+
60
+ def initialize(riff_name, riff_length, riff_type)
61
+ @id, @size, @riff_type = riff_name, riff_length, riff_type
62
+ end
63
+
64
+ def to_s
65
+ "<chunk type:riff id:#{@id} size:#{@size} type:#{@riff_type} />"
66
+ end
67
+
68
+ end
69
+
70
+ class FmtChunk < Chunk
71
+
72
+ attr_accessor :compression_code, :number_of_channels, :sample_rate, :byte_rate, :block_align, :sample_bits, :extra_size, :extra,
73
+ :head_layer, :head_bit_rate, :head_mode, :head_mode_ext, :head_emphasis, :head_flags, :pts_low, :pts_high
74
+
75
+ def parse
76
+ NuWav::WaveFile.log "@raw.size = #{@raw.size}"
77
+ @compression_code = read_word(0)
78
+ @number_of_channels = read_word(2)
79
+ @sample_rate = read_dword(4)
80
+ @byte_rate = read_dword(8)
81
+ @block_align = read_word(12)
82
+ @sample_bits = read_word(14)
83
+ @extra_size = read_word(16)
84
+
85
+ if (@compression_code.to_i == MPEG_COMPRESSION)
86
+ @head_layer = read_word(18)
87
+ @head_bit_rate = read_dword(20)
88
+ @head_mode = read_word(24)
89
+ @head_mode_ext = read_word(26)
90
+ @head_emphasis = read_word(28)
91
+ @head_flags = read_word(30)
92
+ @pts_low = read_dword(32)
93
+ @pts_high = read_dword(36)
94
+ end
95
+ end
96
+
97
+ def to_binary
98
+ out = ''
99
+ out += write_word(@compression_code)
100
+ out += write_word(@number_of_channels)
101
+ out += write_dword(@sample_rate)
102
+ out += write_dword(@byte_rate)
103
+ out += write_word(@block_align)
104
+ out += write_word(@sample_bits)
105
+ out += write_word(@extra_size)
106
+
107
+ if (@compression_code.to_i == MPEG_COMPRESSION)
108
+ out += write_word(@head_layer)
109
+ out += write_dword(@head_bit_rate)
110
+ out += write_word(@head_mode)
111
+ out += write_word(@head_mode_ext)
112
+ out += write_word(@head_emphasis)
113
+ out += write_word(@head_flags)
114
+ out += write_dword(@pts_low)
115
+ out += write_dword(@pts_high)
116
+ end
117
+ "fmt " + write_dword(out.size) + out
118
+ end
119
+
120
+ def to_s
121
+ extra = if (@compression_code.to_i == MPEG_COMPRESSION)
122
+ ", head_layer:#{head_layer}, head_bit_rate:#{head_bit_rate}, head_mode:#{head_mode}, head_mode_ext:#{head_mode_ext}, head_emphasis:#{head_emphasis}, head_flags:#{head_flags}, pts_low:#{pts_low}, pts_high:#{pts_high}"
123
+ else
124
+ ""
125
+ end
126
+ "<chunk type:fmt compression_code:#{compression_code}, number_of_channels:#{number_of_channels}, sample_rate:#{sample_rate}, byte_rate:#{byte_rate}, block_align:#{block_align}, sample_bits:#{sample_bits}, extra_size:#{extra_size} #{extra} />"
127
+ end
128
+ end
129
+
130
+ class FactChunk < Chunk
131
+ attr_accessor :samples_number
132
+
133
+ def parse
134
+ @samples_number = read_dword(0)
135
+ end
136
+
137
+ def to_s
138
+ "<chunk type:fact samples_number:#{@samples_number} />"
139
+ end
140
+
141
+ def to_binary
142
+ "fact" + write_dword(4) + write_dword(@samples_number)
143
+ end
144
+
145
+ end
146
+
147
+ class MextChunk < Chunk
148
+ attr_accessor :sound_information, :frame_size, :ancillary_data_length, :ancillary_data_def, :reserved
149
+
150
+ def parse
151
+ @sound_information = read_word(0)
152
+ @frame_size = read_word(2)
153
+ @ancillary_data_length = read_word(4)
154
+ @ancillary_data_def = read_word(6)
155
+ @reserved = read_char(8,4)
156
+ end
157
+
158
+ def to_s
159
+ "<chunk type:mext sound_information:(#{sound_information}) #{(0..15).inject(''){|s,x| "#{s}#{sound_information[x]}"}}, frame_size:#{frame_size}, ancillary_data_length:#{ancillary_data_length}, ancillary_data_def:#{(0..15).inject(''){|s,x| "#{s}#{ancillary_data_def[x]}"}}, reserved:'#{reserved}' />"
160
+ end
161
+
162
+ def to_binary
163
+ out = "mext" + write_dword(12)
164
+ out += write_word(@sound_information)
165
+ out += write_word(@frame_size)
166
+ out += write_word(@ancillary_data_length)
167
+ out += write_word(@ancillary_data_def)
168
+ out += write_char(@reserved, 4)
169
+ out
170
+ end
171
+ end
172
+
173
+ class BextChunk < Chunk
174
+ attr_accessor :description, :originator, :originator_reference, :origination_date, :origination_time, :time_reference_low, :time_reference_high,
175
+ :version, :umid, :reserved, :coding_history
176
+
177
+ def parse
178
+ @description = read_char(0,256)
179
+ @originator = read_char(256,32)
180
+ @originator_reference = read_char(288,32)
181
+ @origination_date = read_char(320,10)
182
+ @origination_time = read_char(330,8)
183
+ @time_reference_low = read_dword(338)
184
+ @time_reference_high = read_dword(342)
185
+ @version = read_word(346)
186
+ @umid = read_char(348,64)
187
+ @reserved = read_char(412,190)
188
+ @coding_history = read_char(602)
189
+ end
190
+
191
+ def to_s
192
+ "<chunk type:bext description:'#{description}', originator:'#{originator}', originator_reference:'#{originator_reference}', origination_date:'#{origination_date}', origination_time:'#{origination_time}', time_reference_low:#{time_reference_low}, time_reference_high:#{time_reference_high}, version:#{version}, umid:#{umid}, reserved:'#{reserved}', coding_history:#{coding_history} />"
193
+ end
194
+
195
+ def to_binary
196
+ out = "bext" + write_dword(602 + @coding_history.length )
197
+ out += write_char(@description, 256)
198
+ out += write_char(@originator, 32)
199
+ out += write_char(@originator_reference, 32)
200
+ out += write_char(@origination_date, 10)
201
+ out += write_char(@origination_time, 8)
202
+ out += write_dword(@time_reference_low)
203
+ out += write_dword(@time_reference_high)
204
+ out += write_word(@version)
205
+ out += write_char(@umid, 64)
206
+ out += write_char(@reserved, 190)
207
+ out += write_char(@coding_history)
208
+ # make sure coding history ends in '\r\n'
209
+ out
210
+ end
211
+
212
+ end
213
+
214
+ class CartChunk < Chunk
215
+ attr_accessor :version, :title, :artist, :cut_id, :client_id, :category, :classification, :out_cue, :start_date, :start_time, :end_date, :end_time,
216
+ :producer_app_id, :producer_app_version, :user_def, :level_reference, :post_timer, :reserved, :url, :tag_text
217
+
218
+ def parse
219
+ @version = read_char(0,4)
220
+ @title = read_char(4,64)
221
+ @artist = read_char(68,64)
222
+ @cut_id = read_char(132,64)
223
+ @client_id = read_char(196,64)
224
+ @category = read_char(260,64)
225
+ @classification = read_char(324,64)
226
+ @out_cue = read_char(388,64)
227
+ @start_date = read_char(452,10)
228
+ @start_time = read_char(462,8)
229
+ @end_date = read_char(470,10)
230
+ @end_time = read_char(480,8)
231
+ @producer_app_id = read_char(488,64)
232
+ @producer_app_version = read_char(552,64)
233
+ @user_def = read_char(616,64)
234
+ @level_reference = read_dword(680)
235
+ @post_timer = read_char(684,64)
236
+ @reserved = read_char(748,276)
237
+ @url = read_char(1024,1024)
238
+ @tag_text = read_char(2048)
239
+ end
240
+
241
+ def to_s
242
+ "<chunk type:cart version:#{version}, title:#{title}, artist:#{artist}, cut_id:#{cut_id}, client_id:#{client_id}, category:#{category}, classification:#{classification}, out_cue:#{out_cue}, start_date:#{start_date}, start_time:#{start_time}, end_date:#{end_date}, end_time:#{end_time}, producer_app_id:#{producer_app_id}, producer_app_version:#{producer_app_version}, user_def:#{user_def}, level_reference:#{level_reference}, post_timer:#{post_timer}, reserved:#{reserved}, url:#{url}, tag_text:#{tag_text} />"
243
+ end
244
+
245
+ def to_binary
246
+ out = "cart" + write_dword(2048 + @tag_text.length )
247
+ out += write_char(@version,4)
248
+ out += write_char(@title,64)
249
+ out += write_char(@artist,64)
250
+ out += write_char(@cut_id,64)
251
+ out += write_char(@client_id,64)
252
+ out += write_char(@category,64)
253
+ out += write_char(@classification,64)
254
+ out += write_char(@out_cue,64)
255
+ out += write_char(@start_date,10)
256
+ out += write_char(@start_time,8)
257
+ out += write_char(@end_date,10)
258
+ out += write_char(@end_time,8)
259
+ out += write_char(@producer_app_id,64)
260
+ out += write_char(@producer_app_version,64)
261
+ out += write_char(@user_def,64)
262
+ out += write_dword(@level_reference)
263
+ out += write_char(@post_timer,64)
264
+ out += write_char(@reserved,276)
265
+ out += write_char(@url,1024)
266
+ out += write_char(@tag_text)
267
+ out
268
+ end
269
+
270
+ end
271
+
272
+ class DataChunk < Chunk
273
+ attr_accessor :tmp_data_file
274
+
275
+ def self.parse(id, size, file)
276
+
277
+ # tmp_data = File.open('./data_chunk.mp2', 'wb')
278
+ tmp_data = Tempfile.open('data_chunk')
279
+ tmp_data.binmode
280
+
281
+ remaining = size
282
+ while (remaining > 0 && !file.eof?)
283
+ read_bytes = [128, remaining].min
284
+ tmp_data << file.read(read_bytes)
285
+ remaining -= read_bytes
286
+ end
287
+ tmp_data.rewind
288
+ chunk = self.new(id, size, tmp_data)
289
+
290
+ return chunk
291
+ end
292
+
293
+ def self.new_from_file(file)
294
+ tmp_data = Tempfile.open('data_chunk')
295
+ tmp_data.binmode
296
+ FileUtils.cp(file.path, tmp_data.path)
297
+ tmp_data.rewind
298
+ self.new('data', File.size(tmp_data.path).to_s, tmp_data)
299
+ end
300
+
301
+ def initialize(id=nil, size=nil, tmp_data_file=nil)
302
+ @id, @size, @tmp_data_file = id, size, tmp_data_file
303
+ end
304
+
305
+ def data
306
+ f = ''
307
+ if self.tmp_data_file
308
+ NuWav::WaveFile.log "we have a tmp_data_file!"
309
+ self.tmp_data_file.rewind
310
+ f = self.tmp_data_file.read
311
+ self.tmp_data_file.rewind
312
+ else
313
+ NuWav::WaveFile.log "we have NO tmp_data_file!"
314
+ end
315
+ f
316
+ end
317
+
318
+ def to_s
319
+ "<chunk type:data (size:#{data.size})/>"
320
+ end
321
+
322
+ def to_binary
323
+ NuWav::WaveFile.log "data chunk to_binary"
324
+ d = self.data
325
+ NuWav::WaveFile.log "got data size = #{d.size} #{d[0,10]}"
326
+ out = "data" + write_dword(d.size) + d
327
+ out
328
+ end
329
+
330
+ end
331
+ end
@@ -0,0 +1,3 @@
1
+ module NuWav
2
+ VERSION = "0.4.2"
3
+ end
@@ -0,0 +1,286 @@
1
+ module NuWav
2
+
3
+ class WaveFile
4
+
5
+ attr_accessor :header, :chunks
6
+
7
+ def self.parse(wave_file)
8
+ NuWav::WaveFile.new.parse(wave_file)
9
+ end
10
+
11
+ def initialize
12
+ self.chunks = {}
13
+ end
14
+
15
+ def parse(wave_file)
16
+ NuWav::WaveFile.log "Processing wave file #{wave_file.inspect}...."
17
+ wave_file_size = File.size(wave_file)
18
+
19
+ File.open(wave_file, File::RDWR) do |f|
20
+
21
+ #only for windows, make sure we are operating in binary mode
22
+ f.binmode
23
+ #start at the very beginning, a very good place to start
24
+ f.seek(0)
25
+
26
+ riff, riff_length = read_chunk_header(f)
27
+ NuWav::WaveFile.log "riff: #{riff}"
28
+ NuWav::WaveFile.log "riff_length: #{riff_length}"
29
+ NuWav::WaveFile.log "wave_file_size: #{wave_file_size}"
30
+
31
+ raise NotRIFFFormat unless riff == 'RIFF'
32
+ riff_end = [f.tell + riff_length, wave_file_size].min
33
+
34
+ riff_type = f.read(4)
35
+ raise NotWAVEFormat unless riff_type == 'WAVE'
36
+
37
+ @header = RiffChunk.new(riff, riff_length, riff_type)
38
+
39
+ while (f.tell + 8) <= riff_end
40
+ NuWav::WaveFile.log "while #{f.tell} < #{riff_end}"
41
+ chunk_name, chunk_length = read_chunk_header(f)
42
+ fpos = f.tell
43
+
44
+ NuWav::WaveFile.log "found chunk: '#{chunk_name}', size #{chunk_length}"
45
+
46
+ if chunk_name && chunk_length
47
+
48
+ self.chunks[chunk_name.to_sym] = chunk_class(chunk_name).parse(chunk_name, chunk_length, f)
49
+ parsed_chunk_size = self.chunks[chunk_name.to_sym].size
50
+
51
+ NuWav::WaveFile.log "about to do a seek..."
52
+ NuWav::WaveFile.log "f.seek #{fpos} + #{parsed_chunk_size}"
53
+ f.seek(fpos + parsed_chunk_size)
54
+ NuWav::WaveFile.log "seek done"
55
+
56
+ if parsed_chunk_size.odd?
57
+ pad = f.read(1)
58
+ f.seek(fpos + parsed_chunk_size) if (pad.nil? || pad.ord != 0)
59
+ end
60
+
61
+ else
62
+ NuWav::WaveFile.log "chunk or length was off - remainder of file does not parse properly: #{riff_end} - #{fpos} = #{riff_end - fpos}"
63
+ f.seek(riff_end)
64
+ end
65
+ end
66
+ end
67
+ @chunks.each{|k,v| NuWav::WaveFile.log "#{k}: #{v}\n\n" unless k.to_s == 'data'}
68
+ NuWav::WaveFile.log "parse done"
69
+ self
70
+ end
71
+
72
+ def duration
73
+ fmt = @chunks[:fmt]
74
+
75
+ if (PCM_COMPRESSION.include?(fmt.compression_code.to_i))
76
+ data = @chunks[:data]
77
+ data.size / (fmt.sample_rate * fmt.number_of_channels * (fmt.sample_bits / 8))
78
+ elsif (fmt.compression_code.to_i == MPEG_COMPRESSION)
79
+ # <chunk type:fact samples_number:78695424 />
80
+ fact = @chunks[:fact]
81
+ fact.samples_number / fmt.sample_rate
82
+ else
83
+ raise "Duration implemented for PCM and MEPG files only."
84
+ end
85
+ end
86
+
87
+ def is_mpeg?
88
+ (@chunks[:fmt] && (@chunks[:fmt].compression_code.to_i == MPEG_COMPRESSION))
89
+ end
90
+
91
+ def is_pcm?
92
+ (@chunks[:fmt] && (PCM_COMPRESSION.include?(@chunks[:fmt].compression_code.to_i)))
93
+ end
94
+
95
+ def to_s
96
+ out = "NuWav:#{@header}\n"
97
+ out = [:fmt, :fact, :mext, :bext, :cart, :data ].inject(out) do |s, chunk|
98
+ s += "#{self.chunks[chunk]}\n" if self.chunks[chunk]
99
+ s
100
+ end
101
+ end
102
+
103
+ def to_file(file_name, add_extension=false)
104
+ if add_extension && !(file_name =~ /\.wav/)
105
+ file_name += ".wav"
106
+ end
107
+ NuWav::WaveFile.log "NuWav::WaveFile.to_file: file_name = #{file_name}"
108
+
109
+ #get all the chunks together to get final length
110
+ chunks_out = [:fmt, :fact, :mext, :bext, :cart, :data].inject([]) do |list, chunk|
111
+ if self.chunks[chunk]
112
+ out = self.chunks[chunk].to_binary
113
+ NuWav::WaveFile.log out.length
114
+ list << out
115
+ end
116
+ list
117
+ end
118
+
119
+ # TODO: handle other chunks not in the above list, but that might have been in a parsed wav
120
+
121
+ riff_length = chunks_out.inject(0){|sum, chunk| sum += chunk.size}
122
+ NuWav::WaveFile.log "NuWav::WaveFile.to_file: riff_length = #{riff_length}"
123
+
124
+ #open file for writing
125
+ open(file_name, "wb") do |o|
126
+ #write the header
127
+ o << "RIFF"
128
+ o << [(riff_length + 4)].pack('V')
129
+ o << "WAVE"
130
+ #write the chunks
131
+ chunks_out.each{|c| o << c}
132
+ end
133
+
134
+ end
135
+
136
+ def write_data_file(file_name)
137
+ open(file_name, "wb") do |o|
138
+ o << chunks[:data].data
139
+ end
140
+ end
141
+
142
+
143
+ # method to create a wave file using the
144
+ def self.from_mpeg(file_name)
145
+ # read and display infos & tags
146
+ NuWav::WaveFile.log "NuWav::from_mpeg::file_name:#{file_name}"
147
+ mp3info = Mp3Info.open(file_name)
148
+ NuWav::WaveFile.log mp3info
149
+ file = File.open(file_name)
150
+ wave = WaveFile.new
151
+
152
+ # data chunk
153
+ data = DataChunk.new_from_file(file)
154
+ wave.chunks[:data] = data
155
+
156
+ # fmt chunk
157
+ fmt = FmtChunk.new
158
+ fmt.compression_code = MPEG_COMPRESSION
159
+ fmt.number_of_channels = (mp3info.channel_mode == "Single Channel") ? 1 : 2
160
+ fmt.sample_rate = mp3info.samplerate
161
+ fmt.byte_rate = mp3info.bitrate / 8 * 1000
162
+ fmt.block_align = calculate_mpeg_frame_size(mp3info)
163
+ fmt.sample_bits = 65535
164
+ fmt.extra_size = 22
165
+ fmt.head_layer = ACM_LAYERS[mp3info.layer.to_i-1]
166
+ fmt.head_bit_rate = mp3info.bitrate * 1000
167
+ fmt.head_mode = CHANNEL_MODES[mp3info.channel_mode]
168
+ # fmt.head_mode_ext = (mp3info.channel_mode == "JStereo") ? 2**mp3info.mode_extension : 0
169
+ fmt.head_mode_ext = (mp3info.channel_mode == "JStereo") ? 2**mp3info.header[:mode_extension] : 0
170
+ # fmt.head_emphasis = mp3info.emphasis + 1
171
+ fmt.head_emphasis = mp3info.header[:emphasis] + 1
172
+ fmt.head_flags = calculate_mpeg_head_flags(mp3info)
173
+ fmt.pts_low = 0
174
+ fmt.pts_high = 0
175
+ wave.chunks[:fmt] = fmt
176
+ # NuWav::WaveFile.log "fmt: #{fmt}"
177
+
178
+ # fact chunk
179
+ fact = FactChunk.new
180
+ fact.samples_number = calculate_mpeg_samples_number(file, mp3info)
181
+ wave.chunks[:fact] = fact
182
+ # NuWav::WaveFile.log "fact: #{fact}"
183
+
184
+ #mext chunk
185
+ mext = MextChunk.new
186
+ mext.sound_information = 5
187
+ mext.sound_information += 2 if mp3info.header[:padding]
188
+ mext.frame_size = calculate_mpeg_frame_size(mp3info)
189
+ mext.ancillary_data_length = 0
190
+ mext.ancillary_data_def = 0
191
+ wave.chunks[:mext] = mext
192
+ # NuWav::WaveFile.log "mext: #{mext}"
193
+
194
+
195
+ #bext chunk
196
+ bext = BextChunk.new
197
+ bext.time_reference_high = 0
198
+ bext.time_reference_low = 0
199
+ bext.version = 1
200
+ bext.coding_history = "A=MPEG1L#{mp3info.layer},F=#{mp3info.samplerate},B=#{mp3info.bitrate},M=#{CODING_HISTORY_MODE[mp3info.channel_mode]},T=PRX\r\n\0\0"
201
+ wave.chunks[:bext] = bext
202
+ # NuWav::WaveFile.log "bext: #{bext}"
203
+
204
+ #cart chunk
205
+ cart = CartChunk.new
206
+ now = Time.now
207
+ today = Date.today
208
+ later = today << 12
209
+ cart.version = '0101'
210
+ cart.title = File.basename(file_name) # this is just a default
211
+ cart.start_date = today.strftime("%Y-%m-%d")
212
+ cart.start_time = now.strftime("%H:%M:%S")
213
+ cart.end_date = later.strftime("%Y-%m-%d")
214
+ cart.end_time = now.strftime("%H:%M:%S")
215
+ cart.producer_app_id = 'NuWav'
216
+ cart.producer_app_version = '1.0'
217
+ cart.level_reference = 0
218
+ cart.tag_text = "\r\n"
219
+ wave.chunks[:cart] = cart
220
+ # NuWav::WaveFile.log "cart: #{cart}"
221
+ wave
222
+ end
223
+
224
+ def self.calculate_mpeg_samples_number(file, info)
225
+ (File.size(file.path) / calculate_mpeg_frame_size(info)) * Mp3Info::SAMPLES_PER_FRAME[info.layer][info.mpeg_version]
226
+ end
227
+
228
+ def self.calculate_mpeg_head_flags(info)
229
+ flags = 0
230
+ flags += 1 if (info.header[:private_bit])
231
+ flags += 2 if (info.header[:copyright])
232
+ flags += 4 if (info.header[:original])
233
+ flags += 8 if (info.header[:error_protection])
234
+ flags += 16 if (info.mpeg_version > 0)
235
+ flags
236
+ end
237
+
238
+ def self.calculate_mpeg_frame_size(info)
239
+ samples_per_frame = Mp3Info::SAMPLES_PER_FRAME[info.layer][info.mpeg_version]
240
+ ((samples_per_frame / 8) * (info.bitrate * 1000))/info.samplerate
241
+ end
242
+
243
+ protected
244
+
245
+ def read_chunk_header(file)
246
+ hdr = file.read(8)
247
+ chunkName, chunkLen = hdr.unpack("A4V") rescue [nil, nil]
248
+ # NuWav::WaveFile.log "chunkName: '#{chunkName}', chunkLen: '#{chunkLen}'"
249
+ [chunkName, chunkLen]
250
+ end
251
+
252
+ def chunk_class(name)
253
+ begin
254
+ constantize("NuWav::#{camelize("#{name}_chunk")}")
255
+ rescue NameError
256
+ NuWav::Chunk
257
+ end
258
+
259
+ end
260
+
261
+ # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 147
262
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
263
+ if first_letter_in_uppercase
264
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
265
+ else
266
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
267
+ end
268
+ end
269
+
270
+ # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 252
271
+ def constantize(camel_cased_word)
272
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
273
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
274
+ end
275
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
276
+ end
277
+
278
+ def self.log(m)
279
+ if NuWav::DEBUG
280
+ puts "#{Time.now}: NuWav: #{m}"
281
+ end
282
+ end
283
+
284
+ end
285
+
286
+ end