nu_wav 0.1.0

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