├── .gitignore ├── History.txt ├── Manifest.txt ├── README.rdoc ├── Rakefile ├── lib ├── wav-file.rb └── wav-file │ └── wav-file.rb ├── samples ├── adjust_wav_format.rb ├── compare_formats.rb ├── composite.rb ├── connect_wav_files.rb ├── copy_wav.rb ├── double_speed.rb ├── dump_wav.rb ├── half_speed.rb ├── maximize_volume.rb ├── read_data_chunk.rb ├── read_format.rb ├── read_format_and_chunks.rb ├── reverse.rb └── split_channels.rb ├── script ├── console ├── destroy └── generate ├── spec ├── spec.opts ├── spec_helper.rb └── wav-file_spec.rb └── tasks └── rspec.rake /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | pkg/* 3 | ~* 4 | #*# 5 | *.wav 6 | 7 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 0.0.3 2011-06-23 2 | 3 | * works on Ruby1.9.2 4 | 5 | === 0.0.2 2010-11-17 6 | 7 | * bug fix 8 | * add samples 9 | 10 | === 0.0.1 2010-11-17 11 | 12 | * created 13 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | History.txt 2 | Manifest.txt 3 | README.rdoc 4 | Rakefile 5 | lib/wav-file.rb 6 | lib/wav-file/wav-file.rb 7 | script/console 8 | script/destroy 9 | script/generate 10 | spec/spec.opts 11 | spec/spec_helper.rb 12 | spec/wav-file_spec.rb 13 | tasks/rspec.rake 14 | samples/reverse.rb 15 | samples/double_speed.rb 16 | samples/read_data_chunk.rb 17 | samples/dump_wav.rb 18 | samples/connect_wav_files.rb 19 | samples/read_format_and_chunks.rb 20 | samples/copy_wav.rb 21 | samples/compare_formats.rb 22 | samples/adjust_wav_format.rb 23 | samples/split_channels.rb 24 | samples/maximize_volume.rb 25 | samples/composite.rb 26 | samples/read_format.rb 27 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = wav-file 2 | 3 | * http://github.com/shokai/ruby-wav-file 4 | 5 | == DESCRIPTION: 6 | 7 | Read wav file format and Edit data chunk. 8 | 9 | 10 | == ENVIRONMENT: 11 | 12 | * Ruby 1.8.7 13 | * Ruby 1.9.2 14 | * JRuby 1.6.0 15 | 16 | 17 | == INSTALL: 18 | 19 | * gem install wav-file 20 | 21 | == TIPS: 22 | 23 | To run samples, you need wav file. 24 | "ffmpeg" is good tool for making wav file from mp3. 25 | 26 | % ffmpeg -i input.mp3 output.wav 27 | 28 | Wav-file gem cannot convert format of wav file. 29 | Before edit data-chunk of wav files, you should adjust them using ffmpeg. 30 | 31 | 32 | == SYNOPSIS: 33 | 34 | 35 | Read format and chunks 36 | 37 | require 'rubygems' 38 | require 'wav-file' 39 | 40 | f = open("input.wav") 41 | format = WavFile::readFormat(f) 42 | dataChunk = WavFile::readDataChunk(f) 43 | f.close 44 | 45 | puts format 46 | 47 | 48 | Extract wav from binary 49 | 50 | bit = 's*' if format.bitPerSample == 16 # int16_t 51 | bit = 'c*' if format.bitPerSample == 8 # signed char 52 | wavs = dataChunk.data.unpack(bit) # read binary 53 | 54 | 55 | Change volume half 56 | 57 | wavs = wavs.map{|w| w/2} 58 | 59 | 60 | Reverse wav and encode to binary 61 | 62 | dataChunk.data = wavs.reverse.pack(bit) # reverse 63 | 64 | 65 | Save to file 66 | 67 | open("output.wav", "w"){|out| 68 | WavFile::write(out, format, [dataChunk]) 69 | } 70 | 71 | See "samples" directory. 72 | 73 | == LICENSE: 74 | 75 | (The MIT License) 76 | 77 | Copyright (c) 2010 Sho Hashimoto 78 | 79 | Permission is hereby granted, free of charge, to any person obtaining 80 | a copy of this software and associated documentation files (the 81 | 'Software'), to deal in the Software without restriction, including 82 | without limitation the rights to use, copy, modify, merge, publish, 83 | distribute, sublicense, and/or sell copies of the Software, and to 84 | permit persons to whom the Software is furnished to do so, subject to 85 | the following conditions: 86 | 87 | The above copyright notice and this permission notice shall be 88 | included in all copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 91 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 92 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 93 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 94 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 95 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 96 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gem 'hoe', '>= 2.1.0' 3 | require 'hoe' 4 | require 'fileutils' 5 | require './lib/wav-file' 6 | 7 | Hoe.plugin :newgem 8 | # Hoe.plugin :website 9 | # Hoe.plugin :cucumberfeatures 10 | 11 | # Generate all the Rake tasks 12 | # Run 'rake -T' to see list of generated tasks (from gem root directory) 13 | $hoe = Hoe.spec 'wav-file' do 14 | self.developer 'Sho Hashimoto', 'hashimoto@shokai.org' 15 | self.rubyforge_name = self.name # TODO this is default value 16 | # self.extra_deps = [['activesupport','>= 2.0.2']] 17 | 18 | end 19 | 20 | require 'newgem/tasks' 21 | Dir['tasks/**/*.rake'].each { |t| load t } 22 | 23 | # TODO - want other tests/tasks run by default? Add them to the list 24 | # remove_task :default 25 | # task :default => [:spec, :features] 26 | -------------------------------------------------------------------------------- /lib/wav-file.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__)+'/wav-file/wav-file.rb' 2 | 3 | module WavFile 4 | VERSION = '0.0.3' 5 | end 6 | -------------------------------------------------------------------------------- /lib/wav-file/wav-file.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | module WavFile 3 | 4 | class WavFormatError < StandardError 5 | end 6 | 7 | class Chunk 8 | attr_accessor(:name, :size, :data) 9 | 10 | def initialize(file) 11 | @name = file.read(4) 12 | @size = file.read(4).unpack("V")[0].to_i 13 | @data = file.read(@size) 14 | end 15 | 16 | def to_bin 17 | @name + [@data.size].pack('V') + @data 18 | end 19 | end 20 | 21 | class Format 22 | attr_accessor(:id, :channel, :hz, :bytePerSec, :blockSize, :bitPerSample) 23 | 24 | def initialize(chunk) 25 | return if chunk.class != Chunk 26 | return if chunk.name != 'fmt ' 27 | @id = chunk.data.slice(0,2).unpack('c')[0] 28 | @channel = chunk.data.slice(2,2).unpack('c')[0] 29 | @hz = chunk.data.slice(4,4).unpack('V').join.to_i 30 | @bytePerSec = chunk.data.slice(8,4).unpack('V').join.to_i 31 | @blockSize = chunk.data.slice(12,2).unpack('c')[0] 32 | @bitPerSample = chunk.data.slice(14,2).unpack('c')[0] 33 | end 34 | 35 | def to_s 36 | < put "input1.wav" on 1,3,10(sec)' 9 | exit 1 10 | end 11 | 12 | out_file = ARGV.pop 13 | base_file = ARGV.shift 14 | input_file =ARGV.shift 15 | times = ARGV.map{|i|i.to_f}.uniq.sort 16 | 17 | 18 | format1, data1 = WavFile::read open(base_file) 19 | format2, data2 = WavFile::read open(input_file) 20 | 21 | puts format1.to_s 22 | if format1 != format2 23 | puts 'file formats are different!' 24 | puts format2.to_s 25 | exit 1 26 | end 27 | 28 | 29 | bit = 's*' if format1.bitPerSample == 16 # int16_t 30 | bit = 'c*' if format1.bitPerSample == 8 # signed char 31 | wavs1 = data1.data.unpack(bit) 32 | wavs2 = data2.data.unpack(bit) 33 | 34 | times.each{|t| 35 | offset = format1.hz * t * format1.channel 36 | next if offset+wavs2.size > wavs1.size 37 | print "#{t}(sec)..." 38 | for i in 0..wavs2.size-1 do 39 | wav1 = wavs1[i+offset] 40 | wav2 = wavs2[i] * 0.3 # volume down 41 | wavs1[i+offset] = ((wav1+wav2)/2).to_i 42 | end 43 | puts '' 44 | } 45 | 46 | data1.data = wavs1.pack(bit) 47 | 48 | open(out_file, "w"){|out| 49 | WavFile::write(out, format1, [data1]) 50 | } 51 | -------------------------------------------------------------------------------- /samples/connect_wav_files.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # connect files 3 | require 'rubygems' 4 | require 'wav-file' 5 | 6 | if ARGV.size < 3 7 | puts 'ruby connect_wav_files.rb input1.wav input2.wav output.wav' 8 | exit 1 9 | end 10 | 11 | out_file = ARGV.pop 12 | 13 | formats = ARGV.uniq.map{|file_name| 14 | WavFile::readFormat open(file_name) 15 | } 16 | for i in 0..formats.size-2 do 17 | if formats[i] != formats[i+1] 18 | puts 'different format!' 19 | exit 1 20 | end 21 | end 22 | 23 | format = formats.first 24 | dataChunk = WavFile::readDataChunk(open(ARGV.shift)) 25 | 26 | ARGV.each{|f| 27 | data = WavFile::readDataChunk(open(f)) 28 | dataChunk.data += data.data 29 | } 30 | 31 | puts format 32 | 33 | open(out_file, "w"){|out| 34 | WavFile::write(out, format, [dataChunk]) 35 | } 36 | -------------------------------------------------------------------------------- /samples/copy_wav.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # read and write 3 | require 'rubygems' 4 | require 'wav-file' 5 | 6 | if ARGV.size < 2 7 | puts 'ruby copy_wav.rb input.wav output.wav' 8 | exit 1 9 | end 10 | 11 | f = open(ARGV.shift) 12 | format, chunks = WavFile::readAll(f) 13 | f.close 14 | 15 | puts format.to_s 16 | chunks.each{|c| 17 | puts "#{c.name} #{c.size}" 18 | } 19 | 20 | open(ARGV.shift, "w"){|out| 21 | WavFile::write(out, format, chunks) 22 | } 23 | -------------------------------------------------------------------------------- /samples/double_speed.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- coding: utf-8 -*- 3 | # double speed 4 | require 'rubygems' 5 | require 'wav-file' 6 | 7 | if ARGV.size < 2 8 | puts 'ruby double_speed.rb input.rb output.wav' 9 | exit 1 10 | end 11 | 12 | f = open(ARGV.shift) 13 | format, chunks = WavFile::readAll(f) 14 | f.close 15 | 16 | puts format.to_s 17 | puts '===double speed===' 18 | format.hz *= 2 19 | format.bytePerSec *= 2 20 | puts format.to_s 21 | 22 | 23 | open(ARGV.shift, "w"){|out| 24 | WavFile::write(out, format, chunks) 25 | } 26 | 27 | -------------------------------------------------------------------------------- /samples/dump_wav.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # using this script and Excel, you can make a graph. 3 | # => http://www.flickr.com/photos/shokai/4081858478/sizes/o/ 4 | 5 | require 'rubygems' 6 | require 'wav-file' 7 | 8 | if ARGV.size < 2 9 | puts 'ruby dump_wav.rb input.wav dump.txt' 10 | exit 1 11 | end 12 | 13 | format = nil 14 | dataChunk = nil 15 | File.open(ARGV[0]){|file| 16 | format, chunks = WavFile::readAll(file) 17 | puts format.to_s 18 | chunks.each{|c| 19 | puts "chunk - #{c.name} #{c.size}" 20 | dataChunk = c if c.name == 'data' 21 | } 22 | } 23 | 24 | bit = 's*' if format.bitPerSample == 16 # int16_t 25 | bit = 'c*' if format.bitPerSample == 8 # signed char 26 | wavs = dataChunk.data.unpack(bit) # read binary 27 | 28 | open(ARGV[1],'w'){|dump| 29 | wavs.each{|i| 30 | dump.puts i 31 | puts i 32 | } 33 | } 34 | puts wavs.size 35 | 36 | -------------------------------------------------------------------------------- /samples/half_speed.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- coding: utf-8 -*- 3 | # half speed 4 | require 'rubygems' 5 | require 'wav-file' 6 | 7 | if ARGV.size < 2 8 | puts 'ruby half_speed.rb input.rb output.wav' 9 | exit 1 10 | end 11 | 12 | f = open(ARGV.shift) 13 | format, chunks = WavFile::readAll(f) 14 | f.close 15 | 16 | puts format.to_s 17 | puts '===half speed===' 18 | format.hz /= 2 19 | format.bytePerSec /= 2 20 | puts format.to_s 21 | 22 | 23 | open(ARGV.shift, "w"){|out| 24 | WavFile::write(out, format, chunks) 25 | } 26 | 27 | -------------------------------------------------------------------------------- /samples/maximize_volume.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # maximize volume 3 | require 'rubygems' 4 | require 'wav-file' 5 | 6 | if ARGV.size < 2 7 | puts 'ruby maximize_volume.rb input.wav output.wav' 8 | exit 1 9 | end 10 | 11 | in_file = ARGV.shift 12 | out_file = ARGV.shift 13 | 14 | format, data = WavFile::read open(in_file) 15 | 16 | puts format.to_s 17 | 18 | bit = 's*' if format.bitPerSample == 16 # int16_t 19 | bit = 'c*' if format.bitPerSample == 8 # signed char 20 | wavs = data.data.unpack(bit) 21 | 22 | puts "max of this file: #{wavs.max}" 23 | 24 | volume_ratio = 32768/wavs.max.to_f if format.bitPerSample == 16 25 | volume_ratio = 128/wavs.max.to_f if format.bitPerSample == 8 26 | puts "maximize ratio: #{volume_ratio}" 27 | 28 | wavs_fixed = wavs.map{|w| 29 | (w*volume_ratio).to_i 30 | } 31 | puts "maximized: #{wavs_fixed.max}" 32 | 33 | data.data = wavs_fixed.pack(bit) 34 | 35 | open(out_file, "w"){|out| 36 | WavFile::write(out, format, [data]) 37 | } 38 | -------------------------------------------------------------------------------- /samples/read_data_chunk.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # wav file has multiple chunks. 3 | # using WavFile::readDataChunk, you can find data chunk from chunks 4 | require 'rubygems' 5 | require 'wav-file' 6 | 7 | if ARGV.size < 1 8 | puts 'ruby read_data_chunk.rb input.wav' 9 | exit 1 10 | end 11 | 12 | File.open(ARGV[0]){|file| 13 | format = WavFile::readFormat(file) 14 | puts format.to_s 15 | 16 | dataChunk = WavFile::readDataChunk(file) 17 | puts "#{dataChunk.name} #{dataChunk.size}" 18 | } 19 | -------------------------------------------------------------------------------- /samples/read_format.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'wav-file' 4 | 5 | if ARGV.size < 1 6 | puts 'ruby readWav.rb input.wav' 7 | exit 1 8 | end 9 | 10 | File.open(ARGV[0]){|file| 11 | format = WavFile::readFormat(file) 12 | puts format 13 | } 14 | -------------------------------------------------------------------------------- /samples/read_format_and_chunks.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'wav-file' 4 | 5 | if ARGV.size < 1 6 | puts 'ruby read_format_and_chanks.rb input.wav' 7 | exit 1 8 | end 9 | 10 | File.open(ARGV.first){|file| 11 | format, chunks = WavFile::readAll(file) 12 | puts format.to_s 13 | chunks.each{|c| 14 | puts "chunk - #{c.name} #{c.size}" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/reverse.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # reverse wav file 3 | 4 | require 'rubygems' 5 | require 'wav-file' 6 | 7 | if ARGV.size < 2 8 | puts 'ruby reverse.rb input.rb output.wav' 9 | exit 1 10 | end 11 | 12 | f = open(ARGV.shift) 13 | format = WavFile::readFormat(f) 14 | dataChunk = WavFile::readDataChunk(f) 15 | f.close 16 | 17 | puts format 18 | 19 | if dataChunk == nil 20 | puts 'no data chunk' 21 | exit 1 22 | end 23 | 24 | bit = 's*' if format.bitPerSample == 16 # int16_t 25 | bit = 'c*' if format.bitPerSample == 8 # signed char 26 | wavs = dataChunk.data.unpack(bit) # read binary 27 | dataChunk.data = wavs.reverse.pack(bit) # reverse 28 | 29 | open(ARGV.shift, "w"){|out| 30 | WavFile::write(out, format, [dataChunk]) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /samples/split_channels.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- coding: utf-8 -*- 3 | # split left/right channels. 4 | require 'rubygems' 5 | require 'wav-file' 6 | 7 | if ARGV.size < 2 8 | puts 'e.g. ruby splitChannelWav.rb input.rb output_left.wav output_right.wav' 9 | puts ' => make 2 wav files' 10 | exit 1 11 | end 12 | 13 | f = open(ARGV.shift) 14 | format, chunks = WavFile::readAll(f) 15 | f.close 16 | 17 | puts format.to_s 18 | 19 | dataChunk = nil 20 | chunks.each{|c| 21 | puts "#{c.name} #{c.size}" 22 | dataChunk = c if c.name == 'data' # find data chank 23 | } 24 | if dataChunk == nil 25 | puts 'no data chunk' 26 | exit 1 27 | end 28 | 29 | bit = 's*' if format.bitPerSample == 16 # int16_t 30 | bit = 'c*' if format.bitPerSample == 8 # signed char 31 | wavs = dataChunk.data.unpack(bit) # read binary 32 | 33 | off = 32768 if format.bitPerSample == 16 34 | off = 128 if format.bitPerSample == 8 35 | 36 | for i in 0..1 do # LR 37 | wavs_mono = wavs.dup 38 | for j in 0..wavs_mono.size-1 do 39 | if j%2 != i 40 | wavs_mono[j] = off # LRLRLR.. 41 | end 42 | end 43 | dataChunk.data = wavs_mono.pack(bit) 44 | open(ARGV.shift, "w"){|out| 45 | WavFile::write(out, format, [dataChunk]) 46 | } 47 | end 48 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # File: script/console 3 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' 4 | 5 | libs = " -r irb/completion" 6 | # Perhaps use a console_lib to store any extra methods I may want available in the cosole 7 | # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}" 8 | libs << " -r #{File.dirname(__FILE__) + '/../lib/wav-file.rb'}" 9 | puts "Loading wav-file gem" 10 | exec "#{irb} #{libs} --simple-prompt" -------------------------------------------------------------------------------- /script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/destroy' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Destroy.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/generate' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Generate.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'spec' 3 | rescue LoadError 4 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 5 | gem 'rspec' 6 | require 'spec' 7 | end 8 | 9 | $:.unshift(File.dirname(__FILE__) + '/../lib') 10 | require 'wav-file' 11 | -------------------------------------------------------------------------------- /spec/wav-file_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | # Time to add your specs! 4 | # http://rspec.info/ 5 | describe "Place your specs here" do 6 | 7 | it "find this spec in spec directory" do 8 | # violated "Be sure to write your specs" 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'spec' 3 | rescue LoadError 4 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 5 | require 'spec' 6 | end 7 | begin 8 | require 'spec/rake/spectask' 9 | rescue LoadError 10 | puts <<-EOS 11 | To use rspec for testing you must install rspec gem: 12 | gem install rspec 13 | EOS 14 | exit(0) 15 | end 16 | 17 | desc "Run the specs under spec/models" 18 | Spec::Rake::SpecTask.new do |t| 19 | t.spec_opts = ['--options', "spec/spec.opts"] 20 | t.spec_files = FileList['spec/**/*_spec.rb'] 21 | end 22 | --------------------------------------------------------------------------------