--/****************************************************************************
--Software: MIDI CLASS
--Version: 1.0
--Date: 2003/12/23
--Author: Valentin Schmidt
--Contact: fluxus@freenet.de
--License: Freeware
--
--You may use and modify this software as you wish.
--****************************************************************************/
property pTracks --array of tracks, where each track is array of message strings
property pTimebase --timebase = ticks per frame (quarter note)
property pTempo --tempo as integer (0 for unknown)
property pTempoMsgNum --position of tempo event in track 0
property pLF
property pCR
--/****************************************************************************
--* *
--* Public methods *
--* *
--****************************************************************************/
-----------------------------------------------------------------
-- creates (or resets to) new empty MIDI song
-----------------------------------------------------------------
on new (me,timebase)
if voidP(timebase) then timebase=480
me.pTempo = 0 --125000 = 120 bpm
me.pTimebase = timebase
me.pTracks = []
pLF=chr(10) --
pCR=chr(13) --
return me
end
-----------------------------------------------------------------
-- sets tempo by replacing set tempo msg in track 0 (or adding new track 0)
-----------------------------------------------------------------
on setTempo (me,tempo)
tempo = integer(tempo)
if (NOT voidP(me.pTempoMsgNum)) then
me.pTracks[1][me.pTempoMsgNum] = "0 Tempo "&tempo
else
pTempoTrack = ["0 TimeSig 4/4 24 8","0 Tempo "&tempo,"0 Meta TrkEnd"]
--array_unshift(me.pTracks, pTempoTrack)
tmp=[pTempoTrack]
repeat with i = 1 to count(me.pTracks)
tmp.add(me.pTracks[i])
end repeat
me.pTracks=tmp
me.pTempoMsgNum = 1
end if
me.pTempo = tempo
end
-----------------------------------------------------------------
-- returns tempo (0 if not set)
-----------------------------------------------------------------
on getTempo (me)
return me.pTempo
end
-----------------------------------------------------------------
-- sets tempo corresponding to given bpm
-----------------------------------------------------------------
on setBpm (me,bpm)
tempo = integer(60000000/bpm)
me.setTempo(tempo)
end
-----------------------------------------------------------------
-- returns bpm corresponding to tempo
-----------------------------------------------------------------
on getBpm (me)
if (me.pTempo<>0) then return 60000000/me.pTempo
else return 0
end
-----------------------------------------------------------------
-- sets timebase
-----------------------------------------------------------------
on setTimebase (me,tb)
me.pTimebase = tb
end
-----------------------------------------------------------------
-- returns timebase
-----------------------------------------------------------------
on getTimebase (me)
return me.pTimebase
end
-----------------------------------------------------------------
-- adds new track, returns new track count
-----------------------------------------------------------------
on newTrack (me)
tracks=me.pTracks
tracks.add([])
me.pTracks=tracks
return count(me.pTracks)
end
-----------------------------------------------------------------
-- returns track tn as array of msg strings
-----------------------------------------------------------------
on getTrack (me,tn)
return me.pTracks[tn]
end
-----------------------------------------------------------------
-- returns number of messages of track tn
-----------------------------------------------------------------
on getMsgCount (me,tn)
return count(me.pTracks[tn])
end
-----------------------------------------------------------------
-- adds message to end of track tn
-----------------------------------------------------------------
on addMsg (me,tn, msgStr, ttype) --0:absolute, 1:delta
if voidP(ttype) then ttype=0
track = me.pTracks[tn]
if (ttype=1) then
last = me._getTime(track.getLast())
msg = explode(" ",msgStr)
dt = integer(msg[1])
msg[1] = last + dt
msgStr = implode(" ",msg)
end if
track.add(msgStr)
me.pTracks[tn] = track
end
-----------------------------------------------------------------
-- adds message at adequate position of track n (slower than addMsg)
-----------------------------------------------------------------
on insertMsg (me,tn,msgStr)
time = me._getTime(msgStr)
track = me.pTracks[tn]
mc = count(track)
repeat with i=1 to mc
t = me._getTime(track[i])
if (t>=time) then exit repeat
end repeat
array_splice(me.pTracks[tn], i, 0, msgStr)
end
-----------------------------------------------------------------
-- returns message number mn of track tn
-----------------------------------------------------------------
on getMsg (me,tn,mn)
return me.pTracks[tn][mn]
end
-----------------------------------------------------------------
-- deletes message number mn of track tn
-----------------------------------------------------------------
on deleteMsg (me,tn,mn)
array_splice(me.pTracks[tn], mn, 1)
end
-----------------------------------------------------------------
-- deletes track tn
-----------------------------------------------------------------
on deleteTrack (me,tn)
array_splice(me.pTracks, tn, 1)
return count(me.pTracks)
end
-----------------------------------------------------------------
-- deletes track tn
-----------------------------------------------------------------
on getTrackCount (me)
return count(me.pTracks)
end
-----------------------------------------------------------------
-- deletes all tracks except track tn (and track 1 which contains tempo info)
-----------------------------------------------------------------
on soloTrack (me,tn)
if (tn=1) then me.pTracks = [me.pTracks[1]]
else me.pTracks = [me.pTracks[1],me.pTracks[tn]]
end
-----------------------------------------------------------------
-- transposes song by dn half tone steps
-----------------------------------------------------------------
on transpose (me,dn)
tc = count(me.pTracks)
repeat with i = 1 to tc
transposeTrack(i,dn)
end repeat
end
-----------------------------------------------------------------
-- transposes track tn by dn half tone steps
-----------------------------------------------------------------
on transposeTrack (me,tn, dn)
track = me.pTracks[tn]
mc = count(track)
repeat with i = 1 to mc
msg = explode(" ",track[i])
if (msg[2] = "On" OR msg[2] = "Off") then
do(msg[4]) -- n
n = max(0,min(127,n+dn))
msg[4] = "n=n"
track[i] = join(" ",msg)
end if
end repeat
me.pTracks[tn] = track
end
-----------------------------------------------------------------
-- import whole MIDI song as text (mf2t-format)
-----------------------------------------------------------------
on importTxt (me,txt)
if NOT (txt starts "MFile") then me._err(">>> no valid MIDI text!")
txt = trim(txt)
-- make unix text format
if (txt contains pCR) AND NOT (txt contains pLF) then -- MAC
txt = str_replace(pCR,pLF,txt)
else -- PC?
txt = str_replace(pCR,"",txt)
end if
txt = txt&pLF-- makes things easier
repeat with trackstr in trackstrings
track = explode(pLF,trackstr)
track.deleteAt(track.count)
track.deleteAt(track.count)
if (track[1]="TimestampType=Delta") then--delta
track.deleteAt(1)
track = me._delta2Absolute(track)
end if
tracks.add(track)
end repeat
me.pTracks = tracks
me._findTempo()
end
-----------------------------------------------------------------
-- imports track as text (mf2t-format)
-----------------------------------------------------------------
on importTrackTxt (me,txt, tn)
txt = trim(txt)
-- make unix text format
if (txt contains pCR) AND NOT (txt contains pLF) then -- MAC
txt = str_replace(pCR,pLF,txt)
else -- maybe PC, 0D 0A?
txt = str_replace(pCR,"",txt)
end if
track = explode(pLF,txt)
if (track[1]="MTrk") then track.deleteAt(1)
if (track.getLast()="TrkEnd") then track.deleteAt(track.count)
if (track[1]="TimestampType=Delta") then --delta
track.deleteAt(1)
track = me._delta2Absolute(track)
end if
if voidP(tn) then tn=count(me.pTracks)
me.pTracks[tn] = track
if (tn=0) then me._findTempo()
end
-----------------------------------------------------------------
-- returns MIDI song as text
-----------------------------------------------------------------
on getTxt (me,ttype,ret) --0:absolute, 1:delta
if voidP(ttype) then ttype=0
if voidP(ret) then ret=pLF
timebase = me.pTimebase
tracks = me.pTracks
tc = count(me.pTracks)
type=integer(tc>1)
str = "MFile" && type && tc && timebase &ret
repeat with i=1 to tc
str=str& me.getTrackTxt(i,ttype,ret)
end repeat
return str
end
-----------------------------------------------------------------
-- returns track as text
-----------------------------------------------------------------
on getTrackTxt (me,tn,ttype,ret) --0:absolute, 1:delta
if voidP(ttype) then ttype=0
if voidP(ret) then ret=pLF
track = me.pTracks[tn]
str = "MTrk" &ret
if (ttype=1) then --time as delta
str=str& "TimestampType=Delta" &ret
last = 0
repeat with msg in track
t=integer(msg.word[1])
dt=t-last
delete word 1 of msg
str=str& dt && msg &ret
last = t
end repeat
else
repeat with msg in track
str=str& msg &ret
end repeat
end if
str=str& "TrkEnd" &ret
return str
end
-----------------------------------------------------------------
-- imports Standard MIDI File (typ 0 or 1) (and RMID)
-- (if optional parameter tn set, only track tn is imported)
-----------------------------------------------------------------
on importMid (me,smf_path,tn)
song = readBinFile(smf_path) -- Standard MIDI File, typ 0 or 1, or RMID
if (offset("MThd",song)>1) then delete char 1 to offset("MThd",song)-1 of song -- get rid of RMID header
header = song.char[1..14]
if NOT (header starts "MThd"&chr(0)&chr(0)&chr(0)&chr(6)) then me._err(">>> wrong MIDI-header!")
type = ord(header.char[10])
if (type>1) then me._err(">>> only SMF Typ 0 and 1 supported!")
--trackCnt = ord(header[10])*256 + ord(header[11]) --ignore
timebase = ord(header.char[13])*256 + ord(header.char[14])
--me.pType = type
me.pTimebase = timebase
me.pTempo = 0 -- maybe (hopefully!) overwritten by _parseTrack
trackstrings = explode("MTrk",song)
trackstrings.deleteAt(1)
tracks = []
tsc = count(trackstrings)
if (NOT voidP(tn)) then
if (tn>=tsc) then me._err(">>> SMF has less tracks than "&tn&"!")
tracks.add(me._parseTrack(trackstrings[tn],tn))
else
repeat with i = 1 to tsc
tracks.add(me._parseTrack(trackstrings[i],i))
end repeat
end if
me.pTracks = tracks
end
repeat with i = 1 to tc
track = pTracks[i]
mc = count(track)
time = 0
midStr=midStr& "MTrk"
trackstart = length(midStr)
last = ""
repeat with j = 1 to mc
lin = track[j]
t = me._getTime(lin)
dt = t - time
time = t
midStr=midStr& me._writeVarLen(dt)
-- REPETITION: same event, same channel, omit first byte (smaller file size)
str = me._getMsgStr(lin)
start = ord(str.char[1])
if (start=last AND start>=128 AND start<=239) then --239 --159
delete char 1 of str
end if
last = start
midStr=midStr& str
end repeat
trackLen = length(midStr) - trackstart
midStr = midStr.char[1..trackstart] & me._getBytes(trackLen,4) & midStr.char[trackstart+1..length(midStr)] --???
end repeat
return midStr
end
-----------------------------------------------------------------
-- saves MIDI song as Standard MIDI File
-----------------------------------------------------------------
on saveMidFile (me,mid_path)
if (count(me.pTracks)<1) then me._err("MIDI song has no tracks!")
--writeFile(me.getMid(),mid_path)
saveTxtFile(mid_path,me.getMid())
end
-----------------------------------------------------------------
-- returns time code of message string
-----------------------------------------------------------------
on _getTime (me,msgStr)
return integer(msgStr.word[1]) --strtok(msgStr," "))
end
"Pb": -- 0x0E = PitchBend
do(msg[3]) -- chan
do(msg[4]) -- val (2 Bytes!)
a = v mod 256
b = 64 + (v - a)/128
return chr(224+ch-1)&chr(a)&chr(b)--0xE0
-- META EVENTS
"Seqnr": -- 0x00 = sequence_number
num = chr(msg[3])
return chr(255)&chr(0)&chr(2)&num --"xFFx00x02"&num
"Meta":
type = msg[3]
case (type) of
-- 0x01: Meta Text
-- 0x02: Meta Copyright
-- 0x03: Meta TrackName ???SeqName???
-- 0x04: Meta InstrumentName
-- 0x05: Meta Lyrics
-- 0x06: Meta Marker
-- 0x07: Meta Cue
"Text","Copyright","TrkName","InstrName","Lyric","Marker","Cue":
texttypes = ["Text","Copyright","TrkName","InstrName","Lyric","Marker","Cue"]
byte = chr(texttypes.getPos(type))
start = offset(QUOTE,lin)+1
ende = start+ offset(QUOTE,lin.char[start..length(lin)])-2
txt = lin.char[start..ende]
len = chr(length(txt))
return chr(255)&byte&len&txt --"xFF"
"SeqSpec": -- 0x7F = Sequencer specific data (eg: 0 SeqSpec 00 00 41)
cnt = count(msg)-2
data = ""
repeat with i = 1 to cnt
data=data& me._hex2bin(msg[i+2])
end repeat
len = chr(length(data))
return chr(255)&chr(127) &len&data --"xFFx7F"
"SysEx": -- 0xF0 = SysEx
start = offset("f0",lin)+3
ende = start+offset("f7",lin.char[start..length(lin)-1])
data = lin.char[start..ende]
data = me._hex2bin(str_replace(" ","",data))
len = chr(length(data))
return chr(240) &len&data --"xF0"
otherwise:
me._err(">>> unknown event: "&msg[2])
halt()
end case
end
-----------------------------------------------------------------
-- converts binary track string to track (list of msg strings)
-----------------------------------------------------------------
on _parseTrack (me,binStr, tn)
trackLen = length(binStr)
p=4 +1
time = 0
track = []
last=""
repeat while (p -- timedelta
a=[p]
dt = me._readVarLen(binStr,a)
p=a[1]
otherwise:
case (byte) of
255: --0xFF: -- Meta
meta = ord(binStr.char[p+1])
case (meta) of
0: --0x00: -- sequence_number
num = ord(binStr.char[p+2])
track.add(time && "Seqnr "&num)
p=p+2
1,2,3,4,5,6,7:
texttypes = ["Text","Copyright","TrkName","InstrName","Lyric","Marker","Cue"]
type = texttypes[meta]
len = ord(binStr.char[p+2])
txt = binStr.char[p+3..p+3+len-1]
81: --0x51: -- Tempo
tempo = ord(binStr.char[p+3])*256*256 + ord(binStr.char[p+4])*256 + ord(binStr.char[p+5])
track.add(time && "Tempo "&tempo)
if (tn=0 AND time=0) then
me.pTempo = tempo-- ???
me.pTempoMsgNum = count(track) - 1
end if
p=p+6
84: --0x54: -- SMPTE offset
h = ord(binStr.char[p+3])
m = ord(binStr.char[p+4])
s = ord(binStr.char[p+5])
f = ord(binStr.char[p+6])
fh = ord(binStr.char[p+7])
track.add(time && "SMPTE"&&h&&m&&s&&f&&fh)
p=p+8
88: --0x58: -- TimeSig
z = ord(binStr.char[p+3])
t = integer(power(2,ord(binStr.char[p+4])))
mc = ord(binStr.char[p+5])
c = ord(binStr.char[p+6])
track.add(time && "TimeSig "&z&"/"&t&&mc&&c)
p=p+7
127: --0x7F: -- Sequencer specific data (string or hexString???)
len = ord(binStr.char[p+2])
data=""
repeat with i = p+3 to p+3+len-1
data=data& " "&int2hex(ord(binStr.char[i]))
end repeat
track.add(time && "SeqSpec"& data)
p=p+len+3
otherwise:
me._err(">>> unknown meta event:" && time && byte && meta)
end case -- Ende Meta
240: --0xF0: -- SysEx
len = ord(binStr.char[p+1])
str = "f0"
repeat with i=1 to len
str=str && strtolower(int2hex(ord(binStr.char[p+2+i])))
end repeat
track.add(time && "SysEx "&str)
p=p+len+2
otherwise: --Repetition of last event?
case (last) of
"On","Off":
note = ord(binStr.char[p])
vel = ord(binStr.char[p+1])
track.add(time && last && "ch="&chan&" n="¬e&" v="&vel)
p=p+2
otherwise:
me._err(">>> unknown repetition:" && last) --???
end case
end case
end case
end repeat
return track
end
-----------------------------------------------------------------
-- search track 0 for set tempo msg
-----------------------------------------------------------------
on _findTempo (me)
track = me.pTracks[1]
mc = count(track)
repeat with i=1 to mc
msg = explode(" ",track[i])
if (integer(msg[1])>0) then exit repeat
if (msg[2]="Tempo") then
me.pTempo = msg[3]
me.pTempoMsgNum = i
exit repeat
end if
end repeat
end
-----------------------------------------------------------------
-- hexstr to binstr
-----------------------------------------------------------------
on _hex2bin (me,hex_str)
bin_str=""
l=length(hex_str)/2-1
repeat with i = 0 to l
bin_str=bin_str& chr(hexdec(hex_str.char[i*2+1..i*2+2]))
end repeat
return bin_str
end
-----------------------------------------------------------------
-- int to bytes (length len)
-----------------------------------------------------------------
on _getBytes (me,n,len)
str=""
repeat with i = len-1 down to 0
str=str& chr(floor(n/power(256,i)))
end repeat
return str
end
-----------------------------------------------------------------
-- variable length string to int (+repositioning)
-----------------------------------------------------------------
on _readVarLen (me,str,posArray) --&pos !!!
pos=posArray[1]
value=ord(str.char[pos])
pos=pos +1
if bitAnd(value,128) then
value=bitAnd(value,127)
repeat while TRUE
c = ord(str.char[pos])
pos=pos +1
value = value*128 + bitAnd(c,127)
if NOT bitAnd(c,128) then exit repeat
end repeat
end if
posArray[1]=pos
return (value)
end
-----------------------------------------------------------------
-- int to variable length string
-----------------------------------------------------------------
on _writeVarLen (me,value)
buf = bitAnd(value,127)
str=""
repeat while (value/128>0)
value=value/128
buf=buf*256
buf = bitOr(buf, bitOr(bitAnd(value,127),128))
end repeat
repeat while TRUE
str=str& chr(buf mod 256)
if bitAnd(buf,128) then buf=buf/256
else exit repeat
end repeat
return str
end
-----------------------------------------------------------------
-- converts all delta times in track to absolute times
-----------------------------------------------------------------
on _delta2Absolute (me,track)
mc = count(track)
last = 0
repeat with i=1 to mc
msg=track[i]
t=last+integer(msg.word[1])
delete word 1 of msg
track[i]=t && msg
last=t
end repeat
return track
end
-----------------------------------------------------------------
-- error message
-----------------------------------------------------------------
on _err (me,str)
--die(str)
cursor(-1)
alert(str)
abort()
end
Contact
MMI
36 South Court Sq
Suite 300
Newnan, GA 30263
USA