hi all
here is a little module for parsing midi files. it seems to work, but
it's much slower than its python counterpart. maybe somebody could
look through it and see if i'm making any obvious mistakes?
peace
stm
{-------------------------------------------------------------------------- ------------------
A sequence is a tuple (formattype, timedivision, tracks) where
*) formattype is in [0..2]
*) timedivision is either [ticksperbeat] where ticksperbeat is a
nonnegative integer < (2^15)
or [framespersecond, ticksperframe] where framespersecond,
ticksperframe are nonnegative
integers with framespersecond < (2^7) and ticksperframe < (2^8)
*) tracks is a list of Events
An Event is either (ChannelEvent deltatime eventtype channel
parameters)
or (MetaEvent deltatime metatype message) or (SysexEvent deltatime
channel msg) where
*) deltatime is a nonnegative integer
*) eventtype is in [0..7]
*) channel is a nonnegative integer < (2^4)
*) parameters is a list whose elements are nonnegative integers <
(2^7)
*) metatype is a nonnegative integer < (2^7)
*) message is a string
*) channel is in [0..15]
*) msg is a list whose elements are nonnegative integers <
(2^8)
The eventtypes and parameters of ChannelEvents have the following
verbal handles:
eventtype parameters
0 = noteoff [notenumber, velocity]
1 = noteon [notenumber, velocity]
2 = noteaftertouch [notenumber, amount]
3 = controller [controllertype, value]
4 = programchange [programnumber]
5 = channelaftertouch [amount]
6 = pitchbend [valueLSB, valueMSB]
--------------------------------------------------------------------------- ------------------}
module Hugs where
import System.IO
readmidi filepath = (openBinaryFile filepath ReadMode) >>=
hGetContents
writemidi filepath text = (openBinaryFile filepath WriteMode) >>=
(\handle -> hPutStr handle text)
test filepath1 filepath2 = (readmidi filepath1) >>=
(return . parse) >>=
(return . unparse) >>=
(writemidi filepath2)
chr = toEnum :: Int -> Char
ord = fromEnum :: Char -> Int
zeropadded digits minlength = (replicate (minlength - length digits)
0) ++ digits
noleadingzeros digits = until done loop digits
where
done (d:ds) = (d /= 0) || (null ds)
loop (d:ds) = ds
number2digits number base = until done loop [number]
where
done (d:ds) = (d < base)
loop (d:ds) = [div d base, mod d base] ++ ds
digits2number digits base = sum (zipWith (*) (reverse (noleadingzeros
digits)) basepowers)
where
basepowers = 1 : (map (base *) basepowers)
number2fixedlength number minlength = zeropadded (number2digits number
(2^8)) minlength
fixedlength2number digits = digits2number digits (2^8)
number2variablelength number = zipWith (+) digits padding
where
digits = number2digits number (2^7)
padding = (replicate (length digits - 1) (2^7)) ++ [0]
variablelength2number variablelength = digits2number digits (2^7)
where
padding = (replicate (length variablelength - 1) (2^7)) ++ [0]
digits = zipWith (-) variablelength padding
largebyte number = (number >= (2^7))
smallbyte number = (number < (2^7))
getfixedlength numbers numbytes = splitAt numbytes numbers
getvariablelength numbers = (variablelength, remainder)
where
(v, small : remainder) = span largebyte numbers
variablelength = v ++ [small]
analyzetimedivision [byte1, byte2] = if indicator==0 then
[ticksperbeat] else
if indicator==1 then
[framespersecond, ticksperframe] else undefined
where
indicator = div byte1 (2^7)
framespersecond = mod byte1 (2^7)
ticksperframe = byte2
ticksperbeat = (2^8) * byte1 + byte2
synthesizetimedivision [ticksperbeat] = [byte1, byte2]
where
byte1 = div ticksperbeat (2^8)
byte2 = mod ticksperbeat (2^8)
synthesizetimedivision [framespersecond, ticksperframe] = [byte1,
byte2]
where
byte1 = (2^7) + framespersecond
byte2 = ticksperframe
analyzeheaderdata [a,b,c,d,e,f] = (formattype, numtracks,
timedivision)
where
formattype = fixedlength2number [a,b]
numtracks = fixedlength2number [c,d]
timedivision = analyzetimedivision [e,f]
synthesizeheaderdata (formattype, numtracks, timedivision) = f ++ n ++
t
where
f = number2fixedlength formattype 2
n = number2fixedlength numtracks 2
t = synthesizetimedivision timedivision
analyzestatus statusbyte = (eventtype, channel)
where
number = statusbyte - (2^7)
eventtype = div number (2^4)
channel = mod number (2^4)
synthesizestatus (eventtype, channel) = [statusbyte]
where
statusbyte = (2^7) + (2^4) * eventtype + channel
data Event = ChannelEvent Int Int Int [Int] | MetaEvent Int Int String
| SysexEvent Int Int [Int]
deriving (Eq, Show)
synthesizeevent (ChannelEvent deltatime eventtype channel parameters)
= d ++ s ++ parameters
where
d = number2variablelength deltatime
s = synthesizestatus (eventtype, channel)
synthesizeevent (MetaEvent deltatime metatype message) = d ++ s ++
[metatype] ++ m
where
d = number2variablelength deltatime
s = synthesizestatus (7, 15)
m = number2variablelength (length message) ++ (map ord
message)
synthesizeevent (SysexEvent deltatime channel msg) = d ++ s ++ m
where
d = number2variablelength deltatime
s = synthesizestatus (7, channel)
m = number2variablelength (length msg) ++ msg
makechunk identifier numbers = identifier ++ (number2fixedlength
(length numbers) 4) ++ numbers
makeheader (formattype, numtracks, timedivision) = makechunk
headeridentifier headerdata
where
headeridentifier = [77, 84, 104, 100]
headerdata = synthesizeheaderdata (formattype, numtracks,
timedivision)
maketrack events = makechunk trackidentifier (concatMap
synthesizeevent events)
where
trackidentifier = [77, 84, 114, 107]
getchunks numbers = fst loopoutput
where
done (chunks, remainder) = (null remainder)
loop (chunks, remainder) = (chunks ++ [chunk], nextremainder)
where
pastChunkID = drop 4 remainder
(chunksize, pastChunkSize) = getfixedlength pastChunkID 4
(chunk, nextremainder) = getfixedlength pastChunkSize
(fixedlength2number chunksize)
loopoutput = until done loop ([], numbers)
getevent (chunk, runningstatus) = (event, remainder,
nextrunningstatus)
where
(timestamp, pastTimeStamp) = getvariablelength chunk
deltatime = variablelength2number timestamp
explicitStatus = if largebyte (head pastTimeStamp) then 1
else 0
(status, pastStatus) = splitAt explicitStatus pastTimeStamp
nextrunningstatus = if null status then runningstatus else
status
(eventtype, channel) = analyzestatus (head nextrunningstatus)
(event, remainder) = case (eventtype, channel) of
(7, 15) -> (MetaEvent deltatime metatype message, pastEvent)
where
metatype : pastMetaType = pastStatus
(messagelength, pastMessageLength) = getvariablelength pastMetaType
mlength = variablelength2number
messagelength
(msg, pastEvent) = getfixedlength
pastMessageLength mlength
message = map chr msg
(7, _) -> (SysexEvent deltatime channel message, pastEvent)
where
(messagelength, pastMessageLength) = getvariablelength pastStatus
mlength = variablelength2number
messagelength
(message, pastEvent) = getfixedlength
pastMessageLength mlength
(_, _) -> (ChannelEvent deltatime eventtype channel parameters,
pastEvent)
where
numparameters = if elem eventtype [4,5] then 1 else 2
(parameters, pastEvent) = getfixedlength pastStatus numparameters
getevents chunk = fst_loopoutput
where
done (events, runningstatus, remainder) = (null remainder)
loop (events, runningstatus, remainder) = (nextevents,
nextrunningstatus, nextremainder)
where
(event, nextremainder, nextrunningstatus) = getevent (remainder,
runningstatus)
nextevents = events ++ [event]
(fst_loopoutput, _, _) = until done loop ([], [], chunk)
parse filecontents = (formattype, timedivision, tracks)
where
headerchunk : trackchunks = getchunks (map ord
filecontents)
(formattype, numtracks, timedivision) = analyzeheaderdata
headerchunk
tracks = map getevents trackchunks
unparse (formattype, timedivision, tracks) = filecontents
where
numtracks = length tracks
header = makeheader (formattype, numtracks, timedivision)
filecontents = map chr (header ++ concatMap maketrack tracks)