# io.rb: Written by Tadayoshi Funaba 1998-2006 # $Id: io.rb,v 1.6 2006-11-10 21:57:55+09 tadf Exp $ module SMF class Sequence class ReadError < StandardError; end class RS class PO def self.u2s(u, w) u -= 2**w if u > 2**(w-1)-1; u end def initialize(str) @str, @index = str, 0 end def rem() @str.length - @index end def eof? () rem <= 0 end def skip(n) @index += n end def getn(n) raise EOFError if rem < n s = @str[@index, n] skip(n) s end def getc raise EOFError if rem < 1 c = @str[@index] skip(1) c end def getl v = 0 begin v <<= 7 c = getc v |= c & 0x7f end until (c & 0x80).zero? v end def geti(n) u = 0 n.times do u <<= 8 c = getc u |= c end u end def geti16() geti(2) end def geti24() geti(3) end def geti32() geti(4) end def to_s() @str.dup end end def initialize(s, cb) @s, @cb = s, cb end def read_header(s) rs = RS::PO.new(s) format = rs.geti16 ntrks = rs.geti16 div1 = rs.getc div2 = rs.getc if (div1 & 0x80) == 0 tc = nil division = div1 << 8 | div2 else tc = 0x100 - div1 division = div2 end @cb.header(format, ntrks, division, tc) end def read_meta(type, data) case type when 0x0 rs = RS::PO.new(data) num = rs.geti16 @cb.sequencenumber(num) when 0x1..0xf case type when 0x1; @cb.generalpurposetext(data) when 0x2; @cb.copyrightnotice(data) when 0x3; @cb.trackname(data) when 0x4; @cb.instrumentname(data) when 0x5; @cb.lyric(data) when 0x6; @cb.marker(data) when 0x7; @cb.cuepoint(data) when 0x8; @cb.programname(data) when 0x9; @cb.devicename(data) when 0xa; @cb.text0a(data) when 0xb; @cb.text0b(data) when 0xc; @cb.text0c(data) when 0xd; @cb.text0d(data) when 0xe; @cb.text0e(data) when 0xf; @cb.text0f(data) end when 0x20 rs = RS::PO.new(data) ch = rs.getc @cb.channelprefix(ch) when 0x21 rs = RS::PO.new(data) num = rs.getc @cb.midiport(num) when 0x2f @cb.endoftrack when 0x51 rs = RS::PO.new(data) tempo = rs.geti24 @cb.settempo(tempo) when 0x54 rs = RS::PO.new(data) hr = rs.getc tc = [24, 25, 29, 30][(hr >> 5) & 0x3] hr &= 0x1f mn = rs.getc se = rs.getc fr = rs.getc ff = rs.getc @cb.smpteoffset(hr, mn, se, fr, ff, tc) when 0x58 rs = RS::PO.new(data) nn = rs.getc dd = rs.getc cc = rs.getc bb = rs.getc @cb.timesignature(nn, dd, cc, bb) when 0x59 rs = RS::PO.new(data) sf = rs.getc mi = rs.getc sf = RS::PO.u2s(sf, 8) @cb.keysignature(sf, mi) when 0x7f @cb.sequencerspecific(data) else @cb.unknownmeta(type, data) end end def read_track(s) @cb.track_start rs = RS::PO.new(s) running = 0 until rs.eof? @cb.delta(rs.getl) stat = rs.getc if (stat & 0x80) == 0 rs.skip(-1) stat = running else case stat when 0x80..0xef; running = stat when 0xf0..0xf7; running = 0 end end case stat when 0x80..0x8f @cb.noteoff(stat & 0xf, rs.getc, rs.getc) when 0x90..0x9f @cb.noteon(stat & 0xf, rs.getc, rs.getc) when 0xa0..0xaf @cb.polyphonickeypressure(stat & 0xf, rs.getc, rs.getc) when 0xb0..0xbf n = rs.getc v = rs.getc if n < 0x78 @cb.controlchange(stat & 0xf, n, v) else case n when 0x78; @cb.allsoundoff(stat & 0xf) when 0x79; @cb.resetallcontrollers(stat & 0xf) when 0x7a; @cb.localcontrol(stat & 0xf, v) when 0x7b; @cb.allnotesoff(stat & 0xf) when 0x7c; @cb.omnioff(stat & 0xf) when 0x7d; @cb.omnion(stat & 0xf) when 0x7e; @cb.monomode(stat & 0xf, v) when 0x7f; @cb.polymode(stat & 0xf) end end when 0xc0..0xcf @cb.programchange(stat & 0xf, rs.getc) when 0xd0..0xdf @cb.channelpressure(stat & 0xf, rs.getc) when 0xe0..0xef lsb = rs.getc msb = rs.getc val = (lsb | msb << 7) - 0x2000 @cb.pitchbendchange(stat & 0xf, val) when 0xf0, 0xf7 len = rs.getl data = rs.getn(len) if stat == 0xf0 @cb.exclusivef0(data) else @cb.exclusivef7(data) end when 0xff type = rs.getc len = rs.getl data = rs.getn(len) read_meta(type, data) else until rs.eof? unless (rs.getc & 0x80) == 0 rs.skip(-1) break end end end end @cb.track_end end private :read_header, :read_meta, :read_track def get_from_macbin begin if @s[0] == 0 && @s[74] == 0 && @s[82] == 0 && @s[65,4] == 'Midi' @s[128,@s[83,4].unpack('N')[0]] end rescue end end def get_from_rmid begin if @s[0,4] == 'RIFF' && @s[8,4] == 'RMID' @s[20,@s[16,4].unpack('V')[0]] end rescue end end private :get_from_macbin, :get_from_rmid def read begin rs = RS::PO.new(get_from_macbin || get_from_rmid || @s) ckid = rs.getn(4) unless ckid == 'MThd' @cb.error('not an SMF') end rs.skip(-4) until rs.eof? ckid = rs.getn(4) leng = rs.geti32 body = rs.getn(leng) case ckid when 'MThd' read_header(body) when 'MTrk' read_track(body) else @cb.unknownchunk(ckid, body) end end rescue EOFError @cb.error('unexpected EOF') end @cb.result end end class WS class PO def self.s2u(s, w) s += 2**w if s < 0; s end # def initialize() @str = '' end def initialize() @arr = [] end # def puts(s) @str << s end # def putc(c) @str << c end def puts(s) @arr << s end def putc(c) @arr << c.chr end def putl(v) s = '' begin s << (v & 0x7f | 0x80) v >>= 7 end until v.zero? s[0] &= 0x7f s.reverse! puts(s) end def puti(n, u) n.times do |i| putc((u >> (n - i - 1) * 8) & 0xff) end end def puti16(u) puti(2, u) end def puti24(u) puti(3, u) end def puti32(u) puti(4, u) end # def to_s() @str.dup end def to_s() @arr.join end end def initialize(o, cb) @o, @cb = o, cb end def read_meta(ev) case ev when SequenceNumber @cb.sequencenumber(ev.num) when Text case ev when GeneralPurposeText; @cb.generalpurposetext(ev.text) when CopyrightNotice; @cb.copyrightnotice(ev.text) when TrackName; @cb.trackname(ev.text) when InstrumentName; @cb.instrumentname(ev.text) when Lyric; @cb.lyric(ev.text) when Marker; @cb.marker(ev.text) when CuePoint; @cb.cuepoint(ev.text) when ProgramName; @cb.programname(ev.text) when DeviceName; @cb.devicename(ev.text) when Text0A; @cb.text0a(ev.text) when Text0B; @cb.text0b(ev.text) when Text0C; @cb.text0c(ev.text) when Text0D; @cb.text0d(ev.text) when Text0E; @cb.text0e(ev.text) when Text0F; @cb.text0f(ev.text) end when ChannelPrefix @cb.channelprefix(ev.ch) when MIDIPort @cb.midiport(ev.num) when EndOfTrack @cb.endoftrack when SetTempo @cb.settempo(ev.tempo) when SMPTEOffset @cb.smpteoffset(ev.hr, ev.mn, ev.se, ev.fr, ev.ff, ev.tc) when TimeSignature @cb.timesignature(ev.nn, ev.dd, ev.cc, ev.bb) when KeySignature @cb.keysignature(ev.sf, ev.mi) when SequencerSpecific @cb.sequencerspecific(ev.data) else @cb.unknownmeta(ev.type, ev.data) end end def read_track(tr) @cb.track_start offset = 0 tr.each do |ev| @cb.delta(ev.offset - offset) case ev when NoteOff if ev.fake? @cb.noteon(ev.ch, ev.note, 0) else @cb.noteoff(ev.ch, ev.note, ev.vel) end when NoteOn @cb.noteon(ev.ch, ev.note, ev.vel) when PolyphonicKeyPressure @cb.polyphonickeypressure(ev.ch, ev.note, ev.val) when ControlChange @cb.controlchange(ev.ch, ev.num, ev.val) when ProgramChange @cb.programchange(ev.ch, ev.num) when ChannelPressure @cb.channelpressure(ev.ch, ev.val) when PitchBendChange @cb.pitchbendchange(ev.ch, ev.val) when AllSoundOff @cb.allsoundoff(ev.ch) when ResetAllControllers @cb.resetallcontrollers(ev.ch) when LocalControl @cb.localcontrol(ev.ch, ev.val) when AllNotesOff @cb.allnotesoff(ev.ch) when OmniOff @cb.omnioff(ev.ch) when OmniOn @cb.omnion(ev.ch) when MonoMode @cb.monomode(ev.ch, ev.val) when PolyMode @cb.polymode(ev.ch) when ExclusiveF0 @cb.exclusivef0(ev.data) when ExclusiveF7 @cb.exclusivef7(ev.data) when Meta read_meta(ev) end offset = ev.offset end @cb.track_end end private :read_meta, :read_track def read @cb.header(@o.format, @o.ntrks, @o.division, @o.tc) @o.each do |ck| case ck when Track read_track(ck) else @cb.unknownchunk(ck.ckid, ck.body) end end @cb.result end end class XSCallback def header(format, ntrks, division, tc) end def track_start() end def track_end() end def unknownchunk(ckid, body) end def delta(delta) end def noteoff(ch, note, vel) end def noteon(ch, note, vel) end def polyphonickeypressure(ch, note, val) end def controlchange(ch, num, val) end def programchange(ch, num) end def channelpressure(ch, val) end def pitchbendchange(ch, val) end def allsoundoff(ch) end def resetallcontrollers(ch) end def localcontrol(ch, val) end def allnotesoff(ch) end def omnioff(ch) end def omnion(ch) end def monomode(ch, val) end def polymode(ch) end def exclusivef0(data) end def exclusivef7(data) end def sequencenumber(num) end def generalpurposetext(text) end def copyrightnotice(text) end def trackname(text) end def instrumentname(text) end def lyric(text) end def marker(text) end def cuepoint(text) end def programname(text) end def devicename(text) end def text0a(text) end def text0b(text) end def text0c(text) end def text0d(text) end def text0e(text) end def text0f(text) end def channelprefix(ch) end def midiport(num) end def endoftrack() end def settempo(tempo) end def smpteoffset(hr, mn, se, fr, ff, tc) end def timesignature(nn, dd, cc, bb) end def keysignature(sf, mi) end def sequencerspecific(data) end def unknownmeta(type, data) end def result() end def error(mesg) end def warn(mesg) end end module Checker def cr(n, v, e, w=nil) unless e === v error("#{n}: out of range") return end if w unless w === v warn("#{n}: out of range") end end end private :cr def header(format, ntrks, division, tc) cr('header/format', format, (0..2**16-1), (0..2)) if format == 0 cr('header/ntrks', ntrks, (0..2**16-1), (1..1)) else cr('header/ntrks', ntrks, (0..2**16-1), (1..2**16-1)) end unless tc cr('header/division', division, (1..2**15-1)) else cr('header/division', division, (1..2**8-1)) cr('header/tc', tc, (0..2**7)) unless [24, 25, 29, 30].include? tc warn('header/tc: invalid format') end end super end def chunk_body(body) unless body.length <= 2**32-1 error('chunk size: too large') end end private :chunk_body def delta(delta) cr('delta', delta, (0..2**28-1)) super end def noteoff(ch, note, vel) cr('noteoff/ch', ch, (0..2**4-1)) cr('noteoff/note', note, (0..2**7-1)) cr('noteoff/vel', vel, (0..2**7-1)) super end def noteon(ch, note, vel) cr('noteon/ch', ch, (0..2**4-1)) cr('noteon/note', note, (0..2**7-1)) cr('noteon/vel', vel, (0..2**7-1)) super end def polyphonickeypressure(ch, note, val) cr('polyphonickeypressure/ch', ch, (0..2**4-1)) cr('polyphonickeypressure/note', note, (0..2**7-1)) cr('polyphonickeypressure/val', val, (0..2**7-1)) super end def controlchange(ch, num, val) cr('controlchange/ch', ch, (0..2**4-1)) cr('controlchange/num', num, (0..0x77)) cr('controlchange/val', val, (0..2**7-1)) super end def programchange(ch, num) cr('programchange/ch', ch, (0..2**4-1)) cr('programchange/num', num, (0..2**7-1)) super end def channelpressure(ch, val) cr('channelpressure/ch', ch, (0..2**4-1)) cr('channelpressure/val', val, (0..2**7-1)) super end def pitchbendchange(ch, val) cr('pitchbendchange/ch', ch, (0..2**4-1)) cr('pitchbendchange/val', val, (-2**13..2**13-1)) super end def no_channelmodemessage(ch, num, val) cr('channelmodemessage/ch', ch, (0..2**4-1)) cr('channelmodemessage/num', num, (0x78..0x7f)) unless num == 0x7e cr('channelmodemessage/val', val, (0..2**7-1)) else cr('channelmodemessage/val', val, (0..2**7-1), (0..16)) end end private :no_channelmodemessage def allsoundoff(ch) no_channelmodemessage(ch, 0x78, 0); super end def resetallcontrollers(ch) no_channelmodemessage(ch, 0x79, 0); super end def localcontrol(ch, val) no_channelmodemessage(ch, 0x7a, val); super end def allnotesoff(ch) no_channelmodemessage(ch, 0x7b, 0); super end def omnioff(ch) no_channelmodemessage(ch, 0x7c, 0); super end def omnion(ch) no_channelmodemessage(ch, 0x7d, 0); super end def monomode(ch, val) no_channelmodemessage(ch, 0x7e, val); super end def polymode(ch) no_channelmodemessage(ch, 0x7f, 0); super end def exclusivef0(data) cr('exclusive size', data.length, (0..2**28-1), (1..2**28-1)) super end def exclusivef7(data) cr('exclusive size', data.length, (0..2**28-1), (1..2**28-1)) super end def sequencenumber(num) cr('sequencenumber/num', num, (0..2**16-1)) super end def no_text(type, text) cr('text size', text.length, (0..2**28-1), (1..2**28-1)) end private :no_text def generalpurposetext(text) no_text(0x1, text); super end def copyrightnotice(text) no_text(0x2, text); super end def trackname(text) no_text(0x3, text); super end def instrumentname(text) no_text(0x4, text); super end def lyric(text) no_text(0x5, text); super end def marker(text) no_text(0x6, text); super end def cuepoint(text) no_text(0x7, text); super end def programname(text) no_text(0x8, text); super end def devicename(text) no_text(0x9, text); super end def text0a(text) no_text(0xa, text); super end def text0b(text) no_text(0xb, text); super end def text0c(text) no_text(0xc, text); super end def text0d(text) no_text(0xd, text); super end def text0e(text) no_text(0xe, text); super end def text0f(text) no_text(0xf, text); super end def channelprefix(ch) cr('channelprefix/ch', ch, (0..2**8-1), (0..2**4-1)) super end def midiport(num) cr('midiport/num', num, (0..2**8-1)) super end def settempo(tempo) cr('settempo/tempo', tempo, (1..2**24-1)) super end def smpteoffset(hr, mn, se, fr, ff, tc) cr('smpteoffset/hr', hr, (0..2**8-1), (1..23)) cr('smpteoffset/mn', mn, (0..2**8-1), (1..59)) cr('smpteoffset/se', se, (0..2**8-1), (1..59)) cr('smpteoffset/fr', fr, (0..2**8-1), (1..29)) cr('smpteoffset/ff', ff, (0..2**8-1), (1..99)) cr('smpteoffset/tc', tc, (0..2**8-1)) unless [24, 25, 29, 30].include? tc warn('smpteoffset/tc: invalid format') end super end def timesignature(nn, dd, cc, bb) cr('timesignature/nn', nn, (1..2**8-1)) cr('timesignature/dd', dd, (0..2**8-1)) cr('timesignature/cc', cc, (1..2**8-1)) cr('timesignature/bb', bb, (1..2**8-1)) super end def keysignature(sf, mi) cr('keysignature/sf', sf, (-2**7..2**7-1)) cr('keysignature/mi', mi, (0..1)) super end def sequencerspecific(data) unless data.length <= 2**28-1 error('sequencerspecific: too large') end super end def unknownmeta(type, data) unless data.length <= 2**28-1 error('unknownmeta: too large') end super end end class Decode < XSCallback # include Checker def header(format, ntrks, division, tc) @sq = Sequence.new(format, division, tc) end def track_start @sq << (@tr = Track.new) @offset = 0 end def delta(delta) @offset += delta end def noteoff(ch, note, vel) @tr << NoteOff.new(@offset, ch, note, vel) end def noteon(ch, note, vel) if vel == 0 @tr << NoteOff.new(@offset, ch, note, nil) else @tr << NoteOn.new(@offset, ch, note, vel) end end def polyphonickeypressure(ch, note, val) @tr << PolyphonicKeyPressure.new(@offset, ch, note, val) end def controlchange(ch, num, val) @tr << ControlChange.new(@offset, ch, num, val) end def programchange(ch, num) @tr << ProgramChange.new(@offset, ch, num) end def channelpressure(ch, val) @tr << ChannelPressure.new(@offset, ch, val) end def pitchbendchange(ch, val) @tr << PitchBendChange.new(@offset, ch, val) end def allsoundoff(ch) @tr << AllSoundOff.new(@offset, ch) end def resetallcontrollers(ch) @tr << ResetAllControllers.new(@offset, ch) end def localcontrol(ch, val) @tr << LocalControl.new(@offset, ch, val) end def allnotesoff(ch) @tr << AllNotesOff.new(@offset, ch) end def omnioff(ch) @tr << OmniOff.new(@offset, ch) end def omnion(ch) @tr << OmniOn.new(@offset, ch) end def monomode(ch, val) @tr << MonoMode.new(@offset, ch, val) end def polymode(ch) @tr << PolyMode.new(@offset, ch) end def exclusivef0(data) @tr << ExclusiveF0.new(@offset, data) end def exclusivef7(data) @tr << ExclusiveF7.new(@offset, data) end def sequencenumber(num) @tr << SequenceNumber.new(@offset, num) end def generalpurposetext(text) @tr << GeneralPurposeText.new(@offset, text) end def copyrightnotice(text) @tr << CopyrightNotice.new(@offset, text) end def trackname(text) @tr << TrackName.new(@offset, text) end def instrumentname(text) @tr << InstrumentName.new(@offset, text) end def lyric(text) @tr << Lyric.new(@offset, text) end def marker(text) @tr << Marker.new(@offset, text) end def cuepoint(text) @tr << CuePoint.new(@offset, text) end def programname(text) @tr << ProgramName.new(@offset, text) end def devicename(text) @tr << DeviceName.new(@offset, text) end def text0a(text) @tr << Text0A.new(@offset, text) end def text0b(text) @tr << Text0B.new(@offset, text) end def text0c(text) @tr << Text0C.new(@offset, text) end def text0d(text) @tr << Text0D.new(@offset, text) end def text0e(text) @tr << Text0E.new(@offset, text) end def text0f(text) @tr << Text0F.new(@offset, text) end def channelprefix(ch) @tr << ChannelPrefix.new(@offset, ch) end def midiport(num) @tr << MIDIPort.new(@offset, num) end def endoftrack @tr << EndOfTrack.new(@offset) end def settempo(tempo) @tr << SetTempo.new(@offset, tempo) end def smpteoffset(hr, mn, se, fr, ff, tc) @tr << SMPTEOffset.new(@offset, hr, mn, se, fr, ff, tc) end def timesignature(nn, dd, cc, bb) @tr << TimeSignature.new(@offset, nn, dd, cc, bb) end def keysignature(sf, mi) @tr << KeySignature.new(@offset, sf, mi) end def sequencerspecific(data) @tr << SequencerSpecific.new(@offset, data) end def result() @sq end def error(mesg) raise ReadError, mesg end end class Encode < XSCallback # include Checker @@replace_noteoff = false def header(format, ntrks, division, tc) @ws = WS::PO.new @ws.puts('MThd') @ws.puti32(6) @ws.puti16(format) @ws.puti16(ntrks) if tc div1 = 0x100 - tc div2 = division else div1 = division >> 8 div2 = division & 0xff end @ws.putc(div1) @ws.putc(div2) end def track_start @ev = WS::PO.new @running = 0 @cieot = false end def chunk_body(body) end private :chunk_body def track_end @ws.puts('MTrk') unless @cieot delta(0) endoftrack end ev = @ev.to_s chunk_body(ev) @ws.puti32(ev.length) @ws.puts(ev) end def unknownchunk(ckid, body) @ws.puts(ckid) chunk_body(ev) @ws.puti32(body.length) @ws.puts(body) end def de @ev.putl(@delta) end def sb(stat) @ev.putc(stat) unless stat == @running case stat when 0x80..0xef; @running = stat when 0xf0..0xf7; @running = 0 end end def db(data) @ev.putc(data & 0x7f) end private :de, :sb, :db def delta(delta) @delta = delta end def noteoff(ch, note, vel) de if @@replace_noteoff sb(ch | 0x90) db(note) db(0) else sb(ch | 0x80) db(note) db(vel) end end def noteon(ch, note, vel) de sb(ch | 0x90) db(note) db(vel) end def polyphonickeypressure(ch, note, val) de sb(ch | 0xa0) db(note) db(val) end def controlchange(ch, num, val) de sb(ch | 0xb0) db(num) db(val) end def programchange(ch, num) de sb(ch | 0xc0) db(num) end def channelpressure(ch, val) de sb(ch | 0xd0) db(val) end def pitchbendchange(ch, val) de sb(ch | 0xe0) val += 0x2000 lsb = val & 0x7f msb = (val >> 7) & 0x7f db(lsb) db(msb) end def channelmodemessage(ch, num, val) de sb(ch | 0xb0) db(num) db(val) end private :channelmodemessage def allsoundoff(ch) channelmodemessage(ch, 0x78, 0) end def resetallcontrollers(ch) channelmodemessage(ch, 0x79, 0) end def localcontrol(ch, val) channelmodemessage(ch, 0x7a, val) end def allnotesoff(ch) channelmodemessage(ch, 0x7b, 0) end def omnioff(ch) channelmodemessage(ch, 0x7c, 0) end def omnion(ch) channelmodemessage(ch, 0x7d, 0) end def monomode(ch, val) channelmodemessage(ch, 0x7e, val) end def polymode(ch) channelmodemessage(ch, 0x7f, 0) end def exclusivef0(data) de @ev.putc(0xf0) @ev.putl(data.length) @ev.puts(data) end def exclusivef7(data) de @ev.putc(0xf7) @ev.putl(data.length) @ev.puts(data) end def sequencenumber(num) de @ev.putc(0xff) @ev.putc(0x0) @ev.putl(2) @ev.puti16(num) end def text(type, text) de @ev.putc(0xff) @ev.putc(type) @ev.putl(text.length) @ev.puts(text) end private :text def generalpurposetext(text) text(0x1, text) end def copyrightnotice(text) text(0x2, text) end def trackname(text) text(0x3, text) end def instrumentname(text) text(0x4, text) end def lyric(text) text(0x5, text) end def marker(text) text(0x6, text) end def cuepoint(text) text(0x7, text) end def programname(text) text(0x8, text) end def devicename(text) text(0x9, text) end def text0a(text) text(0xa, text) end def text0b(text) text(0xb, text) end def text0c(text) text(0xc, text) end def text0d(text) text(0xd, text) end def text0e(text) text(0xe, text) end def text0f(text) text(0xf, text) end def channelprefix(ch) de @ev.putc(0xff) @ev.putc(0x20) @ev.putl(1) @ev.putc(ch) end def midiport(num) de @ev.putc(0xff) @ev.putc(0x21) @ev.putl(1) @ev.putc(num) end def endoftrack de @ev.putc(0xff) @ev.putc(0x2f) @ev.putl(0) @cieot = true end def settempo(tempo) de @ev.putc(0xff) @ev.putc(0x51) @ev.putl(3) @ev.puti24(tempo) end def smpteoffset(hr, mn, se, fr, ff, tc) unless [24, 25, 29, 30].include? tc warn('smpteoffset/tc: invalid format') end de @ev.putc(0xff) @ev.putc(0x54) @ev.putl(5) hr |= ({24=>0, 25=>1, 29=>2, 30=>3}[tc] || 0) << 5 @ev.putc(hr) @ev.putc(mn) @ev.putc(se) @ev.putc(fr) @ev.putc(ff) end def timesignature(nn, dd, cc, bb) de @ev.putc(0xff) @ev.putc(0x58) @ev.putl(4) @ev.putc(nn) @ev.putc(dd) @ev.putc(cc) @ev.putc(bb) end def keysignature(sf, mi) sf = WS::PO.s2u(sf, 8) de @ev.putc(0xff) @ev.putc(0x59) @ev.putl(2) @ev.putc(sf) @ev.putc(mi) end def sequencerspecific(data) de @ev.putc(0xff) @ev.putc(0x7f) @ev.putl(data.length) @ev.puts(data) end def unknownmeta(type, data) de @ev.putc(0xff) @ev.putc(type) @ev.putl(data.length) @ev.puts(data) end def result() @ws.to_s end def error(mesg) raise ReadError, mesg end end class << self def decode(s) self::RS.new(s, self::Decode.new).read end def read(io) decode(io.binmode.read) end def decoceio(io) warn('decodeio is deprecated; use read') if $VERBOSE read(io) end def load(fn) open(fn) do |io| read(io) end end def decodefile(fn) warn('decodefile is deprecated; use load') if $VERBOSE load(fn) end end def encode self.class::WS.new(self, self.class::Encode.new).read end def write(io) io.binmode.write(encode) end def encodeio(io) warn('encodeio is deprecated; use write') if $VERBOSE write(io) end def save(fn) open(fn, 'w') do |io| write(io) end end def encodefile(fn) warn('encodefile is deprecated; use save') if $VERBOSE save(fn) end end end