Skip to content

Instantly share code, notes, and snippets.

@miura1729
Created November 3, 2020 22:30
Show Gist options
  • Save miura1729/1713c25cde3212c8aa7a0f1b1527b6b6 to your computer and use it in GitHub Desktop.
Save miura1729/1713c25cde3212c8aa7a0f1b1527b6b6 to your computer and use it in GitHub Desktop.
SMAF
# SMAF operation class
# Copyright (c) 2002 Hideki MIURA
# You can redistribute it and/or modify it under the same term as Ruby.
#
# Copyright of SMAF belonges to YAMAHA Corpration
#
module SMAF
# Block base class
# This class is common operationes for all block. Block is word in
# this program. Block is chunk, event, sub-block.
#
#
class Block
@@default_hook = Hash.new(nil)
@@default_hook[Block] = {
:before_parse => lambda {|c| },
:after_parse => lambda {|c| },
}
def self.get_default_hook
@@default_hook[self]
end
def self.set_default_hook(key, proc)
@@default_hook[self][key] = proc
end
def self.inherited(sub)
@@default_hook[sub] = @@default_hook[Block].dup
end
def initialize(id, parent)
@parent = parent
@id = id
@data = nil
@childlen = []
@hook = self.class.get_default_hook.dup
end
def init_from_stream(inport)
parse_body(inport)
end
def parse_body(inport)
@hook[:before_parse].call(self)
n = parse_body_no_hook(inport)
@hook[:after_parse].call(self)
n
end
def to_s
"#<Block: #{@id} Size=#{@size}>"
end
attr :parent
def get_childlen
@childlen
end
def get_id
@id
end
def get_size
@size
end
private
def id2class
nil
end
def parse_body_no_hook(inport)
@data = inport.read(@size)
@size
end
end
# Chunk base class
# This class is common operationes for all chunk.
#
# Chunk consists of chunkID, chunkSize, body and crc
#
class Chunk<Block
def init_from_stream(inport)
@size = (inport.read(4).unpack('N'))[0]
@restsize = @size
super
@size + 4
end
def to_s
"#<Chunk: #{@id} Size=#{@size}>"
end
private
def parse_childlen(inport, rest)
chunks = []
while rest > 3 do
id = inport.read(4)
rest = rest - 4
cls = id2class(id)
if cls == nil then
cls = id2class(id[0,3])
end
if cls == nil then
raise "Unknown Chunk id #{id}"
end
nc = cls.new(id, self)
rest -= nc.init_from_stream(inport)
chunks.push nc
end
chunks
end
end
#
# Event Holder Chunk Abstruct Class
#
#
class EventHolderChunk<Chunk
include EventUtil
def initialize(chunk_id, parent)
@timebase = parent.get_timebase_as_msec
super
end
def init_from_stream(inport)
super
end
def get_timebase_as_msec
@timebase
end
private
def id2class(key)
raise "Event Holder Chunk class is abstruct."
end
def parse_body_no_hook(inport)
# Seqence size of 0 data in input stream
seq0size = 0
curtime = 0
orgrest = @restsize
begin
dur = parse_duration(inport)
@restsize -= duration_size(dur)
if dur != 0 then
seq0size = 0
else
seq0size = seq0size + 1
end
curtime = curtime + dur * @timebase
etype = inport.readchar
@restsize -= 1
if etype != 0 then
seq0size = 0
else
seq0size = seq0size + 1
end
ev = make_event(etype, inport, curtime)
if ev then
@childlen.push(ev)
seq0size = 0
end
if @restsize < 2 then
inport.read(@restsize)
@restsize = 0
end
end while @restsize > 1
orgrest
end
end
class ContentsInfoChunk<Chunk; end
class OptionalDataChunk<Chunk; end
class MasterTrackChunk<Chunk; end
class SequenceDataChunk<EventHolderChunk
def initialize(chunk_id, parent)
@timebase = parent.get_timebase_as_msec
super
end
private
def make_event(etype, inport, curtime)
ev = nil
if etype == 255 then
nx = inport.readchar
@restsize -= 1
if nx == 0xf0 then
size = inport.readchar
inport.read(size)
@restsize -= (size + 1)
ev = nil
else
ev = NopMessage.new(0, 0, self, curtime)
end
elsif etype == 0 then
etype = inport.readchar
@restsize -= 1
if etype == 0 then
return nil
end
ch = etype >> 6
case etype & 0x30
when 0x30
val = inport.readchar
@restsize -= 1
cls = {0x0 => ProgramChangeMessage,
0x1 => BankSelectMessage,
0x2 => OctaveShiftMessage,
0x3 => ModulationMessage,
0x4 => PitchBendMessage,
0x7 => VolumeMessage,
0xa => PanMessage,
0xb => ExpressionMessage}[etype & 0xf]
ev = cls.new(ch, val, self, curtime)
when 0x20
val = ((etype & 0xf) + 1) * 0x8
ev = ModulationMessage.new(ch, val, self, curtime)
when 0x10
val = ((etype & 0xf) + 1) * 0x8
ev = PitchBendMessage.new(ch, val, self, curtime)
when 0x0
val = ((etype & 0xf) + 1) * 0x8
ev = ExpressionMessage.new(ch, val, self, curtime)
end
else
gt = parse_duration(inport)
@restsize -= duration_size(gt)
ev = NoteMessage.new(etype, gt, self, curtime)
end
ev
end
end
class ScoreTrackChunk<Chunk
include TimeBaseUtil
ID2Class = {
'MspI' => Chunk,
'Mtsu' => Chunk,
'Mtsq' => SequenceDataChunk
}
def id2class(key)
ID2Class[key]
end
def get_timebase_as_msec
@timebase_d
end
def get_gate_timebase_as_msec
@timebase_g
end
def parse_body_no_hook(inport)
@format_type = inport.readchar
@sequence_type = inport.readchar
case @format_type
when 0
@timebase_d = timebase2msec(inport.readchar)
@timebase_g = timebase2msec(inport.readchar)
data = []
data[0] = inport.readchar
data[1] = inport.readchar
@key_control_status = []
@vibration_status = []
@ch_type = []
(0..1).each do |n|
n2 = n * 2
@key_control_status[n2] = (data[n] >> 7) & 1
@key_control_status[n2 + 1] = (data[n] >> 3) & 1
@vibration_status[n2] = (data[n] >> 6) & 1
@vibration_status[n2 + 1] = (data[n] >> 2) & 1
@ch_type[n2] = (data[n] >> 4) & 3
@ch_type[n2 + 1] = (data[n] >> 0) & 3
end
@childlen = parse_childlen(inport, @size - 6)
end
@size
end
end
class AudioTrackChunk<Chunk; end
#
# Graphics Track Sequence Data Chunk
# See Sepc. 70
#
class GraphicsTrackSequenceDataChunk<EventHolderChunk
def initialize(chunk_id, parent)
@timebase = parent.get_timebase_as_msec
super
end
private
def make_event(etype, inport, curtime)
ev = id2class(etype).new(etype, self, curtime)
@restsize -= ev.init_from_stream(inport)
ev
end
def id2class(key)
case key
when 0
NopEvent
when 1
ResetOriginEvent
when 0x20
BackDropColorDefnitionEvent
when 0x21
OffsetOriginEvent
when 0x22
ControlEvent # User Event
when 0x40..0x7f
DisplayObjectEvent
else
raise "Reserved Evnet Type #{key}"
end
end
end
#
# Image Chunk
# See Spec. 109
#
class ImageChunk<Chunk
include ImageUtil
private
def parse_body_no_hook(inport)
@image = inport.read(@size)
@size
end
end
#
# Bmp Chunk
# See Spec. 110
#
class BmpChunk<Chunk
include ImageUtil
private
def parse_body_no_hook(inport)
@xwidth = inport.readchar
midbyte = inport.readchar
@ywidth = inport.readchar
rsize = @size - 3
@xwidth = @xwidth << 4 + ((midbyte >> 4) & 0xf)
@ywidth = ((midbyte & 0xf) << 8) + @ywidth
@image = inport.read(rsize)
@size
end
end
#
# Link Chunk
# See Spec. 111
#
class LinkChunk<Chunk
end
#
# Image Data Chunk
# See spec. 109
#
class ImageDataChunk<Chunk
private
ID2Class = {
'Gig' => ImageChunk,
'Gmd' => BmpChunk,
'Gln' => LinkChunk
}
def id2class(key)
ID2Class[key]
end
def parse_body_no_hook(inport)
@childlen = parse_childlen(inport, @size)
@size
end
end
#
# Display Parameter Definition Chunk
# See spec. 68p
#
class DisplayParameterDefinitionChunk<Chunk
include EventUtil
include DisplayParameterUtil
attr :data
private
def parse_body_no_hook(inport)
@data = {}
rest = @size
while rest > 0 do
esize = parse_duration(inport)
etype = inport.readchar
@data[etype] = parse_repeat(inport, esize - 1)
rest = rest - esize - duration_size(esize)
end
@size
end
end
#
# Graphics Track Setup Data Chunk
# See spec. 68p
#
class GraphicsTrackSetupDataChunk<Chunk
private
ID2Class = {
'Gdpd' => DisplayParameterDefinitionChunk,
'Gcpd' => Chunk
}
def id2class(key)
ID2Class[key]
end
def parse_body_no_hook(inport)
@childlen = parse_childlen(inport, @size)
@size
end
end
#
# Graphics Track Chunk
# See spec. 65p
#
class GraphicsTrackChunk<Chunk
include TimeBaseUtil
DataOffset = {"Format Type" => 0, "Player Type" => 1,
"Text Encode Type" => 2, "Color Type" => 3,
"Time Base" => 4, "Option Size" => 5, "Option Data" => 6}
def initialize(chunk_id, parent)
@time_base = 1
super
end
def get_data_by_label(label)
@data[DataOffset[label]]
end
def get_timebase_as_msec
timebase2msec(@data[4])
end
private
ID2Class = {
'Gtsu' => GraphicsTrackSetupDataChunk,
'Gsq' => GraphicsTrackSequenceDataChunk,
'Gfid' => Chunk,
'Gimd' => ImageDataChunk,
}
def id2class(key)
ID2Class[key]
end
def parse_body_no_hook(inport)
rest = @size
@data = inport.read(6)
optsize = @data[5].to_i
@data = @data + inport.read(optsize)
rest = rest - optsize - 6
@time_base = get_timebase_as_msec
@childlen = parse_childlen(inport, rest)
@size
end
def to_s
"#<Chunk: #{@id} Size=#{@size} ColorType=#{get_data_by_label('Color Type')}>"
end
end
# File chunk
#
# FileChunk is toplevel chunk in SMAF file
# See SMAF spec 16p (in Japanese)
#
# FileChunk consists of Header (define in chunk.rb), body, CRC
#
class FileChunk<Chunk
#
# ChunkID -> ChunkClass
#
ID2Class = {
'CNTI' => ContentsInfoChunk,
'OPDA' => OptionalDataChunk,
'MSTR' => MasterTrackChunk,
'MTR' => ScoreTrackChunk,
'ATR' => AudioTrackChunk,
'GTR' => GraphicsTrackChunk,
}
private
def id2class(key)
ID2Class[key]
end
def parse_body_no_hook(inport)
@childlen = parse_childlen(inport, @size)
@crc = inport.read(2)
@size + 2
end
end
#
# Chunk generater from stream
#
#
class Parser
def parse(inport)
inport.binmode
id = inport.read(4)
top = FileChunk.new(id, nil)
top.init_from_stream(inport)
top
end
end
end # module SMAF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment