#! /usr/bin/env ruby # smf2wav.rb: Written by Tadayoshi Funaba 1999-2006 # $Id: smf2wav.rb,v 1.20 2006-11-10 21:57:06+09 tadf Exp $ require 'smf' require 'smf/toy/tempomap' require 'gopt' include SMF module SMF class Wave def initialize(rate=8000) @rate = rate @wave = [] end def sin(start, stop, freq, amp) (start..stop).each do |i| r = amp * Math.sin(2 * Math::PI * freq * (i.to_f / @rate)) @wave[i] ||= 0.0 @wave[i] += r end end def dump out = [ 'RIFF', @wave.length + 36, 'WAVE', 'fmt', 16, 1, 1, @rate, @rate, 1, 8, 'data', @wave.length].pack('A4 V A4 A4 V v v V V v v A4 V') @wave.collect!{|x| x || 0.0} peak = @wave.max @wave.each_with_index do |x, i| @wave[i] = 128 + (127 * x / peak).round end out << @wave.pack('C*') out end end class Sequence class EncodeWav < XSCallback FREQ = [] (0..127).each do |n| FREQ << 440 * 2**((n-69.0)/12) end def initialize(tm) @tm = tm @rate = 8000 @wave = Wave.new(@rate) end def track_start @offset = 0 @noteon = [] end def delta(delta) @offset += delta end def noteoff(ch, note, vel) return if ch == 9 # GM perc. val = (@noteon[ch] || {}).delete(note) if val start, vel = val @wave.sin(pos(start), pos(@offset), FREQ[note], vel) end end def noteon(ch, note, vel) return if ch == 9 # GM perc. @noteon[ch] ||= {} @noteon[ch][note] ||= [@offset, vel] end def pos(offset) e = @tm.offset2elapse(offset) (e * @rate).round end def result() @wave.dump end end def encode_wav tm = TempoMap.new(self) self.class::WS.new(self, self.class::EncodeWav.new(tm)).read end def write_wav(io) io.binmode.write(encode_wav) end def save_wav(fn) open(fn, 'w') do |io| write_wav(io) end end end end def usage warn 'usage: smf2wav [-o output] [input]' exit 1 end usage unless opt = Gopt.gopt('o:') usage unless $*.size >= 0 && $*.size <= 1 ifile = $*.shift ifile = nil if ifile == '-' ofile = opt[:o] if opt[:o] ofile ||= File.basename(ifile, '.mid') + '.wav' if ifile ofile = nil if ofile == '-' sq = unless ifile then Sequence.read($stdin) else Sequence.load(ifile) end unless ofile then sq.write_wav($stdout) else sq.save_wav(ofile) end