nu_wav 0.1.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Andrew Kuklewicz (kookster)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ = nu_wav
2
+
3
+ NuWav is a pure Ruby audio WAVE file parser and writer.
4
+
5
+ It currently has support for basic WAVE files, Broadcast Wave Format (bext and mext chunks), and cart chunk.
6
+
7
+ Other chunks types in the WAVE are not yet supported.
8
+
9
+ == Note on Patches/Pull Requests
10
+
11
+ * Fork the project.
12
+ * Make your feature addition or bug fix.
13
+ * Add tests for it. This is important so I don't break it in a
14
+ future version unintentionally.
15
+ * Commit, do not mess with rakefile, version, or history.
16
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
17
+ * Send me a pull request. Bonus points for topic branches.
18
+
19
+ == Copyright
20
+
21
+ Copyright (c) 2010 Andrew Kuklewicz kookster. See LICENSE for details.
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "nu_wav"
8
+ gem.summary = %Q{NuWav is a pure ruby audio WAV file parser and writer.}
9
+ gem.description = %Q{NuWav is a pure ruby audio WAV file parser and writer. It supports Broadcast Wave Format (BWF), inclluding MPEG audio data, and the public radio standard cart chunk.}
10
+ gem.email = "andrew@beginsinwonder.com"
11
+ gem.homepage = "http://github.com/kookster/nu_wav"
12
+ gem.authors = ["kookster"]
13
+ gem.add_dependency('ruby-mp3info', '>= 0.6.13')
14
+ gem.files.exclude ".document"
15
+ gem.files.exclude ".gitignore"
16
+
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/test_*.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ task :test => :check_dependencies
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "nu_wav #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,551 @@
1
+ # http://www.prss.org/contentdepot/automation_specifications.cfm
2
+ # Bill Kelly <billk <at> cts.com> http://article.gmane.org/gmane.comp.lang.ruby.general/43110
3
+
4
+ require 'rubygems'
5
+ require 'mp3info'
6
+ require 'date'
7
+
8
+ module NuWav
9
+
10
+ DEBUG = false
11
+
12
+ PCM_COMPRESSION = 1
13
+ MPEG_COMPRESSION = 80
14
+
15
+ ACM_MPEG_LAYER1 = 1
16
+ ACM_MPEG_LAYER2 = 2
17
+ ACM_MPEG_LAYER3 = 4
18
+
19
+ ACM_LAYERS = [ACM_MPEG_LAYER1, ACM_MPEG_LAYER2, ACM_MPEG_LAYER3]
20
+
21
+ ACM_MPEG_STEREO = 1
22
+ ACM_MPEG_JOINTSTEREO = 2
23
+ ACM_MPEG_DUALCHANNEL = 4
24
+ ACM_MPEG_SINGLECHANNEL= 8
25
+
26
+ CHANNEL_MODES = {'Stereo'=>ACM_MPEG_STEREO, 'JStereo'=>ACM_MPEG_JOINTSTEREO, 'Dual Channel'=>ACM_MPEG_DUALCHANNEL, 'Single Channel'=>ACM_MPEG_SINGLECHANNEL}
27
+
28
+ CODING_HISTORY_MODE = {'Single Channel'=>'mono', 'Stereo'=>'stereo', 'Dual Channel'=>'dual-mono', 'JStereo'=>'joint-stereo'}
29
+
30
+ class NotRIFFFormat < StandardError; end
31
+ class NotWAVEFormat < StandardError; end
32
+
33
+ class WaveFile
34
+
35
+ attr_accessor :header, :chunks
36
+
37
+ def self.parse(wave_file)
38
+ wf = NuWav::WaveFile.new
39
+ wf.parse(wave_file)
40
+ wf
41
+ end
42
+
43
+ def initialize
44
+ self.chunks = {}
45
+ end
46
+
47
+ def parse(wave_file)
48
+ NuWav::WaveFile.log "Processing wave file #{wave_file.inspect}...."
49
+ File.open(wave_file, File::RDWR) do |f|
50
+ #only for windows, make sure we are operating in binary mode
51
+ f.binmode
52
+ #start at the very beginning, a very good place to start
53
+ f.seek(0)
54
+
55
+ riff, riff_length = read_chunk_header(f)
56
+ NuWav::WaveFile.log "riff_length: #{riff_length}"
57
+ raise NotRIFFFormat unless riff == 'RIFF'
58
+ riff_end = f.tell + riff_length
59
+
60
+ riff_type = f.read(4)
61
+ raise NotWAVEFormat unless riff_type == 'WAVE'
62
+
63
+ @header = RiffChunk.new(riff, riff_length, riff_type)
64
+
65
+ while f.tell < riff_end
66
+ chunk_name, chunk_length = read_chunk_header(f)
67
+ fpos = f.tell
68
+
69
+ NuWav::WaveFile.log "found chunk: '#{chunk_name}', size #{chunk_length}"
70
+ self.chunks[chunk_name.to_sym] = chunk_class(chunk_name).parse(chunk_name, chunk_length, f)
71
+
72
+ f.seek(fpos + self.chunks[chunk_name.to_sym].size)
73
+ end
74
+ end
75
+ @chunks.each{|k,v| NuWav::WaveFile.log "#{k}: #{v}\n\n" unless k == :data}
76
+ end
77
+
78
+ def duration
79
+ fmt = @chunks[:fmt]
80
+
81
+ if fmt && (fmt.compression_code.to_i == PCM_COMPRESSION)
82
+ @header.size / (fmt.sample_rate * fmt.number_of_channels * (fmt.sample_bits / 8))
83
+ else
84
+ raise "Duration implemented for WAV files only."
85
+ end
86
+ end
87
+
88
+ def to_s
89
+ out = "NuWav:#{@header}\n"
90
+ out = [:fmt, :fact, :mext, :bext, :cart, :data ].inject(out) do |s, chunk|
91
+ s += "#{self.chunks[chunk]}\n" if self.chunks[chunk]
92
+ s
93
+ end
94
+ end
95
+
96
+ def to_file(file_name, add_extension=false)
97
+ if add_extension && !(file_name =~ /\.wav/)
98
+ file_name += ".wav"
99
+ end
100
+ NuWav::WaveFile.log "NuWav::WaveFile.to_file: file_name = #{file_name}"
101
+
102
+ #get all the chunks together to get final length
103
+ chunks_out = [:fmt, :fact, :mext, :bext, :cart, :data].inject([]) do |list, chunk|
104
+ out = self.chunks[chunk].to_binary
105
+ NuWav::WaveFile.log out.length
106
+ list << out
107
+ list
108
+ end
109
+
110
+ riff_length = chunks_out.inject(0){|sum, chunk| sum += chunk.size}
111
+ NuWav::WaveFile.log "NuWav::WaveFile.to_file: riff_length = #{riff_length}"
112
+
113
+ #open file for writing
114
+ open(file_name, "wb") do |o|
115
+ #write the header
116
+ o << "RIFF"
117
+ o << [(riff_length + 4)].pack('V')
118
+ o << "WAVE"
119
+ #write the chunks
120
+ chunks_out.each{|c| o << c}
121
+ end
122
+
123
+ end
124
+
125
+ def write_data_file(file_name)
126
+ open(file_name, "wb") do |o|
127
+ o << chunks[:data].data
128
+ end
129
+ end
130
+
131
+
132
+ # method to create a wave file using the
133
+ def self.from_mpeg(file_name)
134
+ # read and display infos & tags
135
+ NuWav::WaveFile.log "NuWav::from_mpeg::file_name:#{file_name}"
136
+ mp3info = Mp3Info.open(file_name)
137
+ NuWav::WaveFile.log mp3info
138
+ file = File.open(file_name)
139
+ wave = WaveFile.new
140
+
141
+ # data chunk
142
+ data = DataChunk.new
143
+ data.data = file.read
144
+ wave.chunks[:data] = data
145
+
146
+ # fmt chunk
147
+ fmt = FmtChunk.new
148
+ fmt.compression_code = MPEG_COMPRESSION
149
+ fmt.number_of_channels = (mp3info.channel_mode == "Single Channel") ? 1 : 2
150
+ fmt.sample_rate = mp3info.samplerate
151
+ fmt.byte_rate = mp3info.bitrate / 8 * 1000
152
+ fmt.block_align = calculate_mpeg_frame_size(mp3info)
153
+ fmt.sample_bits = 65535
154
+ fmt.extra_size = 22
155
+ fmt.head_layer = ACM_LAYERS[mp3info.layer.to_i-1]
156
+ fmt.head_bit_rate = mp3info.bitrate * 1000
157
+ fmt.head_mode = CHANNEL_MODES[mp3info.channel_mode]
158
+ # fmt.head_mode_ext = (mp3info.channel_mode == "JStereo") ? 2**mp3info.mode_extension : 0
159
+ fmt.head_mode_ext = (mp3info.channel_mode == "JStereo") ? 2**mp3info.header[:mode_extension] : 0
160
+ # fmt.head_emphasis = mp3info.emphasis + 1
161
+ fmt.head_emphasis = mp3info.header[:emphasis] + 1
162
+ fmt.head_flags = calculate_mpeg_head_flags(mp3info)
163
+ fmt.pts_low = 0
164
+ fmt.pts_high = 0
165
+ wave.chunks[:fmt] = fmt
166
+ # NuWav::WaveFile.log "fmt: #{fmt}"
167
+
168
+ # fact chunk
169
+ fact = FactChunk.new
170
+ fact.samples_number = calculate_mpeg_samples_number(file, mp3info)
171
+ wave.chunks[:fact] = fact
172
+ # NuWav::WaveFile.log "fact: #{fact}"
173
+
174
+ #mext chunk
175
+ mext = MextChunk.new
176
+ mext.sound_information = 5
177
+ mext.sound_information += 2 if mp3info.header[:padding]
178
+ mext.frame_size = calculate_mpeg_frame_size(mp3info)
179
+ mext.ancillary_data_length = 0
180
+ mext.ancillary_data_def = 0
181
+ wave.chunks[:mext] = mext
182
+ # NuWav::WaveFile.log "mext: #{mext}"
183
+
184
+
185
+ #bext chunk
186
+ bext = BextChunk.new
187
+ bext.time_reference_high = 0
188
+ bext.time_reference_low = 0
189
+ bext.version = 1
190
+ 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"
191
+ wave.chunks[:bext] = bext
192
+ # NuWav::WaveFile.log "bext: #{bext}"
193
+
194
+ #cart chunk
195
+ cart = CartChunk.new
196
+ now = Time.now
197
+ today = Date.today
198
+ later = today << 12
199
+ cart.version = '0101'
200
+ cart.title = File.basename(file_name) # this is just a default
201
+ cart.start_date = today.strftime("%Y-%m-%d")
202
+ cart.start_time = now.strftime("%H:%M:%S")
203
+ cart.end_date = later.strftime("%Y-%m-%d")
204
+ cart.end_time = now.strftime("%H:%M:%S")
205
+ cart.producer_app_id = 'PRX'
206
+ cart.producer_app_version = '3.0'
207
+ cart.level_reference = 0
208
+ cart.tag_text = "\r\n"
209
+ wave.chunks[:cart] = cart
210
+ # NuWav::WaveFile.log "cart: #{cart}"
211
+ wave
212
+ end
213
+
214
+ def self.calculate_mpeg_samples_number(file, info)
215
+ (File.size(file.path) / calculate_mpeg_frame_size(info)) * Mp3Info::SAMPLES_PER_FRAME[info.layer][info.mpeg_version]
216
+ end
217
+
218
+ def self.calculate_mpeg_head_flags(info)
219
+ flags = 0
220
+ flags += 1 if (info.header[:private_bit])
221
+ flags += 2 if (info.header[:copyright])
222
+ flags += 4 if (info.header[:original])
223
+ flags += 8 if (info.header[:error_protection])
224
+ flags += 16 if (info.mpeg_version > 0)
225
+ flags
226
+ end
227
+
228
+ def self.calculate_mpeg_frame_size(info)
229
+ samples_per_frame = Mp3Info::SAMPLES_PER_FRAME[info.layer][info.mpeg_version]
230
+ ((samples_per_frame / 8) * (info.bitrate * 1000))/info.samplerate
231
+ end
232
+
233
+ protected
234
+
235
+ def read_chunk_header(file)
236
+ hdr = file.read(8)
237
+ chunkName, chunkLen = hdr.unpack("A4V")
238
+ end
239
+
240
+ def chunk_class(name)
241
+ constantize("NuWav::#{camelize("#{name}_chunk")}")
242
+ end
243
+
244
+ # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 147
245
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
246
+ if first_letter_in_uppercase
247
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
248
+ else
249
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
250
+ end
251
+ end
252
+
253
+ # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 252
254
+ def constantize(camel_cased_word)
255
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
256
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
257
+ end
258
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
259
+ end
260
+
261
+ def self.log(m)
262
+ if NuWav::DEBUG
263
+ puts "#{Time.now}: NuWav: #{m}"
264
+ end
265
+ end
266
+
267
+ end
268
+
269
+ class Chunk
270
+ attr_accessor :id, :size, :raw
271
+
272
+ def self.parse(id, size, file)
273
+ raw = file.read(size)
274
+ chunk = self.new(id, size, raw)
275
+ chunk.parse
276
+ return chunk
277
+ end
278
+
279
+ def initialize(id=nil, size=nil, raw=nil)
280
+ @id, @size, @raw = id, size, raw
281
+ end
282
+
283
+ def parse
284
+ end
285
+
286
+ def read_dword(start)
287
+ @raw[start..(start+3)].unpack('V').first
288
+ end
289
+
290
+ def read_word(start)
291
+ @raw[start..(start+1)].unpack('v').first
292
+ end
293
+
294
+ def read_char(start, length=(@raw.length-start))
295
+ @raw[start..(start+length-1)]
296
+ end
297
+
298
+ def write_dword(val)
299
+ [val].pack('V')
300
+ end
301
+
302
+ def write_word(val)
303
+ [val].pack('v')
304
+ end
305
+
306
+ def write_char(val, length=nil)
307
+ val ||= ''
308
+ length ||= val.length
309
+ # NuWav::WaveFile.log "length:#{length} val.length:#{val.length} val:#{val}"
310
+ padding = "\0" * [(length - val.length), 0].max
311
+ out = val[0,length] + padding
312
+ # NuWav::WaveFile.log out
313
+ out
314
+ end
315
+
316
+ def to_binary
317
+ end
318
+ end
319
+
320
+
321
+ class RiffChunk
322
+ attr_accessor :id, :size, :riff_type
323
+
324
+ def initialize(riff_name, riff_length, riff_type)
325
+ @id, @size, @riff_type = riff_name, riff_length, riff_type
326
+ end
327
+
328
+ def to_s
329
+ "<chunk type:riff id:#{@id} size:#{@size} type:#{@riff_type} />"
330
+ end
331
+
332
+ end
333
+
334
+ class FmtChunk < Chunk
335
+
336
+ attr_accessor :compression_code, :number_of_channels, :sample_rate, :byte_rate, :block_align, :sample_bits, :extra_size, :extra,
337
+ :head_layer, :head_bit_rate, :head_mode, :head_mode_ext, :head_emphasis, :head_flags, :pts_low, :pts_high
338
+
339
+ def parse
340
+ NuWav::WaveFile.log "@raw.size = #{@raw.size}"
341
+ @compression_code = read_word(0)
342
+ @number_of_channels = read_word(2)
343
+ @sample_rate = read_dword(4)
344
+ @byte_rate = read_dword(8)
345
+ @block_align = read_word(12)
346
+ @sample_bits = read_word(14)
347
+ @extra_size = read_word(16)
348
+
349
+ if (@compression_code.to_i == MPEG_COMPRESSION)
350
+ @head_layer = read_word(18)
351
+ @head_bit_rate = read_dword(20)
352
+ @head_mode = read_word(24)
353
+ @head_mode_ext = read_word(26)
354
+ @head_emphasis = read_word(28)
355
+ @head_flags = read_word(30)
356
+ @pts_low = read_dword(32)
357
+ @pts_high = read_dword(36)
358
+ end
359
+ end
360
+
361
+ def to_binary
362
+ out = ''
363
+ out += write_word(@compression_code)
364
+ out += write_word(@number_of_channels)
365
+ out += write_dword(@sample_rate)
366
+ out += write_dword(@byte_rate)
367
+ out += write_word(@block_align)
368
+ out += write_word(@sample_bits)
369
+ out += write_word(@extra_size)
370
+
371
+ if (@compression_code.to_i == MPEG_COMPRESSION)
372
+ out += write_word(@head_layer)
373
+ out += write_dword(@head_bit_rate)
374
+ out += write_word(@head_mode)
375
+ out += write_word(@head_mode_ext)
376
+ out += write_word(@head_emphasis)
377
+ out += write_word(@head_flags)
378
+ out += write_dword(@pts_low)
379
+ out += write_dword(@pts_high)
380
+ end
381
+ "fmt " + write_dword(out.size) + out
382
+ end
383
+
384
+ def to_s
385
+ extra = if (@compression_code.to_i == MPEG_COMPRESSION)
386
+ ", 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}"
387
+ else
388
+ ""
389
+ end
390
+ "<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} />"
391
+ end
392
+ end
393
+
394
+ class FactChunk < Chunk
395
+ attr_accessor :samples_number
396
+
397
+ def parse
398
+ @samples_number = read_dword(0)
399
+ end
400
+
401
+ def to_s
402
+ "<chunk type:fact samples_number:#{@samples_number} />"
403
+ end
404
+
405
+ def to_binary
406
+ "fact" + write_dword(4) + write_dword(@samples_number)
407
+ end
408
+
409
+ end
410
+
411
+ class MextChunk < Chunk
412
+ attr_accessor :sound_information, :frame_size, :ancillary_data_length, :ancillary_data_def, :reserved
413
+
414
+ def parse
415
+ @sound_information = read_word(0)
416
+ @frame_size = read_word(2)
417
+ @ancillary_data_length = read_word(4)
418
+ @ancillary_data_def = read_word(6)
419
+ @reserved = read_char(8,4)
420
+ end
421
+
422
+ def to_s
423
+ "<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}' />"
424
+ end
425
+
426
+ def to_binary
427
+ out = "mext" + write_dword(12)
428
+ out += write_word(@sound_information)
429
+ out += write_word(@frame_size)
430
+ out += write_word(@ancillary_data_length)
431
+ out += write_word(@ancillary_data_def)
432
+ out += write_char(@reserved, 4)
433
+ out
434
+ end
435
+ end
436
+
437
+ class BextChunk < Chunk
438
+ attr_accessor :description, :originator, :originator_reference, :origination_date, :origination_time, :time_reference_low, :time_reference_high,
439
+ :version, :umid, :reserved, :coding_history
440
+
441
+ def parse
442
+ @description = read_char(0,256)
443
+ @originator = read_char(256,32)
444
+ @originator_reference = read_char(288,32)
445
+ @origination_date = read_char(320,10)
446
+ @origination_time = read_char(330,8)
447
+ @time_reference_low = read_dword(338)
448
+ @time_reference_high = read_dword(342)
449
+ @version = read_word(346)
450
+ @umid = read_char(348,64)
451
+ @reserved = read_char(412,190)
452
+ @coding_history = read_char(602)
453
+ end
454
+
455
+ def to_s
456
+ "<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} />"
457
+ end
458
+
459
+ def to_binary
460
+ out = "bext" + write_dword(602 + @coding_history.length )
461
+ out += write_char(@description, 256)
462
+ out += write_char(@originator, 32)
463
+ out += write_char(@originator_reference, 32)
464
+ out += write_char(@origination_date, 10)
465
+ out += write_char(@origination_time, 8)
466
+ out += write_dword(@time_reference_low)
467
+ out += write_dword(@time_reference_high)
468
+ out += write_word(@version)
469
+ out += write_char(@umid, 64)
470
+ out += write_char(@reserved, 190)
471
+ out += write_char(@coding_history)
472
+ # make sure coding history ends in '\r\n'
473
+ out
474
+ end
475
+
476
+ end
477
+
478
+ class CartChunk < Chunk
479
+ attr_accessor :version, :title, :artist, :cut_id, :client_id, :category, :classification, :out_cue, :start_date, :start_time, :end_date, :end_time,
480
+ :producer_app_id, :producer_app_version, :user_def, :level_reference, :post_timer, :reserved, :url, :tag_text
481
+
482
+ def parse
483
+ @version = read_char(0,4)
484
+ @title = read_char(4,64)
485
+ @artist = read_char(68,64)
486
+ @cut_id = read_char(132,64)
487
+ @client_id = read_char(196,64)
488
+ @category = read_char(260,64)
489
+ @classification = read_char(324,64)
490
+ @outcue = read_char(388,64)
491
+ @start_date = read_char(452,10)
492
+ @start_time = read_char(462,8)
493
+ @end_date = read_char(470,10)
494
+ @end_time = read_char(480,8)
495
+ @producer_app_id = read_char(488,64)
496
+ @producer_app_version = read_char(552,64)
497
+ @user_def = read_char(616,64)
498
+ @level_reference = read_dword(680)
499
+ @post_timer = read_char(684,64)
500
+ @reserved = read_char(748,276)
501
+ @url = read_char(1024,1024)
502
+ @tag_text = read_char(2048)
503
+ end
504
+
505
+ def to_s
506
+ "<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} />"
507
+ end
508
+
509
+ def to_binary
510
+ out = "cart" + write_dword(2048 + @tag_text.length )
511
+ out += write_char(@version,4)
512
+ out += write_char(@title,64)
513
+ out += write_char(@artist,64)
514
+ out += write_char(@cut_id,64)
515
+ out += write_char(@client_id,64)
516
+ out += write_char(@category,64)
517
+ out += write_char(@classification,64)
518
+ out += write_char(@outcue,64)
519
+ out += write_char(@start_date,10)
520
+ out += write_char(@start_time,8)
521
+ out += write_char(@end_date,10)
522
+ out += write_char(@end_time,8)
523
+ out += write_char(@producer_app_id,64)
524
+ out += write_char(@producer_app_version,64)
525
+ out += write_char(@user_def,64)
526
+ out += write_dword(@level_reference)
527
+ out += write_char(@post_timer,64)
528
+ out += write_char(@reserved,276)
529
+ out += write_char(@url,1024)
530
+ out += write_char(@tag_text)
531
+ out
532
+ end
533
+
534
+ end
535
+
536
+ class DataChunk < Chunk
537
+ alias_method :data, :raw
538
+ alias_method :data=, :raw=
539
+
540
+
541
+ def to_s
542
+ "<chunk type:data (size:#{data.size})/>"
543
+ end
544
+
545
+ def to_binary
546
+ out = "data" + write_dword(data.size) + data
547
+ end
548
+
549
+ end
550
+
551
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'nu_wav'
7
+
8
+ class Test::Unit::TestCase
9
+ end
@@ -0,0 +1,46 @@
1
+ require 'helper'
2
+
3
+ class TestNuWav < Test::Unit::TestCase
4
+ def test_parse_wav
5
+ assert true
6
+ end
7
+
8
+ def test_parse_wav_with_bwf
9
+ assert true
10
+ end
11
+
12
+ def test_parse_wav_with_bwf_and_cart_chunk
13
+ assert true
14
+ end
15
+ end
16
+
17
+ # if NuWav::DEBUG
18
+ #
19
+ # wf = NuWav::WaveFile.new
20
+ # # wf.parse('/Users/akuklewicz/dev/testaudio/0330AK_Studded.wav')
21
+ # # puts "wf.duration = #{wf.duration}"
22
+ # # puts "wf = #{wf}"
23
+ #
24
+ # wf.parse('/Users/akuklewicz/dev/workspace/mediajoint/test/fixtures/files/AfropopW_040_SGMT01.wav')
25
+ #
26
+ # puts "--------------------------------------------------------------------------------"
27
+ #
28
+ # wf.write_data_file('/Users/akuklewicz/dev/workspace/mediajoint/test/fixtures/files/AfropopW_040_SGMT01.mp2')
29
+ #
30
+ # # wf.to_file('AK_FreshA05_160_SGMT02')
31
+ # # wf.parse('AK_FreshA05_160_SGMT02.wav')
32
+ #
33
+ # puts "--------------------------------------------------------------------------------"
34
+ # #
35
+ #
36
+ # wv = NuWav::WaveFile.from_mpeg('/Users/akuklewicz/dev/workspace/mediajoint/test/fixtures/files/AK_AfropopW_040_SGMT01.mp2')
37
+ # wv.to_file('AK_AfropopW_040_SGMT01_to_file_test.wav')
38
+ #
39
+ # puts "--------------------------------------------------------------------------------"
40
+ #
41
+ # wf = NuWav::WaveFile.new
42
+ # wf.parse('AK_AfropopW_040_SGMT01_to_file_test.wav')
43
+ #
44
+ #
45
+ #
46
+ # end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nu_wav
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kookster
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-04-03 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ruby-mp3info
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.6.13
24
+ version:
25
+ description: NuWav is a pure ruby audio WAV file parser and writer. It supports Broadcast Wave Format (BWF), inclluding MPEG audio data, and the public radio standard cart chunk.
26
+ email: andrew@beginsinwonder.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - LICENSE
36
+ - README.rdoc
37
+ - Rakefile
38
+ - VERSION
39
+ - lib/nu_wav.rb
40
+ - test/helper.rb
41
+ - test/test_nu_wav.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/kookster/nu_wav
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.5
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: NuWav is a pure ruby audio WAV file parser and writer.
70
+ test_files:
71
+ - test/helper.rb
72
+ - test/test_nu_wav.rb