Skip to content

Commit 0686c4c

Browse files
ParserMonad:
* define ParseError * let ParserMonad be public (problem with inline function using it otherwise, for now) * add readUInt32be Read: implement header parsing
1 parent 11c2c99 commit 0686c4c

File tree

5 files changed

+115
-31
lines changed

5 files changed

+115
-31
lines changed

src/ZMidi/DataTypes.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type MidiFormat =
2323
/// Format 2 file - 1 or more independent tracks.
2424
| MidiFormat2
2525
type MidiTimeDivision =
26-
| FPS of frame: word16
26+
| FramePerSecond of frame: word16
2727
| TicksPerBeat of ticks: word16
2828

2929
type MidiHeader = {

src/ZMidi/Internal/ParserMonad.fs

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,20 @@ module ParserMonad =
2626
type ErrMsg =
2727
| EOF of where: string
2828
| Other of error: string
29+
2930
type State =
3031
{ Position: Pos
3132
RunningStatus: VoiceEvent
3233
}
3334

35+
type ParseError = ParseError of position: Pos * message: ErrMsg
3436

3537
type ParserMonad<'a> =
36-
private ParserMonad of (MidiData -> State -> Result<'a * State, ErrMsg> )
38+
ParserMonad of (MidiData -> State -> Result<'a * State, ParseError> )
3739

3840
let inline private apply1 (parser : ParserMonad<'a>)
3941
(midiData : byte[])
40-
(state : State) : Result<'a * State, ErrMsg> =
42+
(state : State) : Result<'a * State, ParseError> =
4143
let (ParserMonad fn) = parser in fn midiData state
4244

4345
let inline mreturn (x:'a) : ParserMonad<'a> =
@@ -51,7 +53,7 @@ module ParserMonad =
5153
| Ok (ans, st1) -> apply1 (next ans) input st1
5254

5355
let mzero () : ParserMonad<'a> =
54-
ParserMonad <| fun _ _ -> Error (EOF "mzero")
56+
ParserMonad <| fun _ state -> Error (ParseError(state.Position, EOF "mzero"))
5557

5658
let inline mplus (parser1 : ParserMonad<'a>) (parser2 : ParserMonad<'a>) : ParserMonad<'a> =
5759
ParserMonad <| fun input state ->
@@ -73,7 +75,7 @@ module ParserMonad =
7375
let (parseMidi:ParserBuilder) = new ParserBuilder()
7476

7577
/// Run the parser on a file.
76-
let runParseMidi (ma : ParserMonad<'a>) (inputPath : string) : Result<'a, ErrMsg> =
78+
let runParseMidi (ma : ParserMonad<'a>) (inputPath : string) : Result<'a, ParseError> =
7779
use stream = File.Open(path = inputPath, mode = FileMode.Open, access = FileAccess.Read)
7880
use memory = new MemoryStream()
7981
stream.CopyTo(memory)
@@ -84,15 +86,17 @@ module ParserMonad =
8486

8587
/// Throw a parse error
8688
let parseError (genMessage : Pos -> string) : ParserMonad<'a> =
87-
ParserMonad <| fun _ st -> Error (Other (genMessage st.Position))
89+
ParserMonad <| fun _ st -> Error (ParseError(st.Position, Other (genMessage st.Position)))
8890

8991
/// Run the parser, if it fails swap the error message.
9092
let ( <??> ) (parser : ParserMonad<'a>) (genMessage : Pos -> string) : ParserMonad<'a> =
9193
ParserMonad <| fun input st ->
9294
match apply1 parser input st with
9395
| Ok result -> Ok result
94-
| Error _ -> Error (Other (genMessage st.Position))
96+
| Error _ -> Error (ParseError(st.Position, Other (genMessage st.Position)))
9597

98+
let fatalError err =
99+
ParserMonad <| fun _ st -> Error (ParseError(st.Position, err))
96100

97101
let getRunningEvent : ParserMonad<VoiceEvent> =
98102
ParserMonad <| fun _ st -> Ok (st.RunningStatus , st)
@@ -109,16 +113,16 @@ module ParserMonad =
109113
else
110114
PositionInvalid
111115

112-
let inline private checkedParseM (name: string) (f: MidiData -> State -> Result<('a * State), ErrMsg>) =
113-
ParserMonad
114-
(fun input state ->
115-
try
116-
match input,state with
117-
| PositionValid -> f input state
118-
| PositionInvalid -> Error (EOF name)
119-
with
120-
| e -> Error (Other (sprintf "%A" e))
121-
)
116+
let inline private checkedParseM (name: string) (f: MidiData -> State -> Result<('a * State), ParseError>) =
117+
ParserMonad
118+
(fun input state ->
119+
try
120+
match input,state with
121+
| PositionValid -> f input state
122+
| PositionInvalid -> Error (ParseError(state.Position, EOF name))
123+
with
124+
| e -> Error (ParseError(state.Position, (Other (sprintf "%A" e))))
125+
)
122126

123127
let peek : ParserMonad<byte> =
124128
checkedParseM "peek" <|
@@ -141,8 +145,8 @@ module ParserMonad =
141145
ParserMonad <| fun input state ->
142146
let rec work (i : int)
143147
(st : State)
144-
(fk : ErrMsg -> Result<'a list * State, ErrMsg>)
145-
(sk : State -> 'a list -> Result<'a list * State, ErrMsg>) =
148+
(fk : ParseError -> Result<'a list * State, ParseError>)
149+
(sk : State -> 'a list -> Result<'a list * State, ParseError>) =
146150
if i <= 0 then
147151
sk st []
148152
else
@@ -154,7 +158,7 @@ module ParserMonad =
154158
work length state (fun msg -> Error msg) (fun st ac -> Ok (ac, st))
155159
|> Result.map (fun (ans, st) -> (List.toArray ans, st))
156160

157-
/// Drop a byte (word8)
161+
/// Drop a byte (word8).
158162
let dropByte : ParserMonad<unit> =
159163
checkedParseM "dropByte" <|
160164
fun input st -> Ok ((), { st with Position = st.Position + 1 })
@@ -181,20 +185,30 @@ module ParserMonad =
181185
}
182186
<??> sprintf "readString failed at %i"
183187

184-
185188
// Parse a uint16 (big endian).
186-
let readUint16be : ParserMonad<uint16>=
189+
let readUInt16be : ParserMonad<uint16>=
187190
parseMidi {
188191
let! a = readByte
189192
let! b = readByte
190-
return uint16be a b
193+
return word16be a b
191194
}
192195
<??> sprintf "uint16be: failed at %i"
193196

197+
// Parse a word14 (big endian) from 2 consecutive bytes.
194198
let readWord14be =
195199
parseMidi {
196200
let! a = readByte
197201
let! b = readByte
198202
return (word14be a b)
199203
}
200-
<??> sprintf "word14be: failed at %i"
204+
<??> sprintf "word14be: failed at %i"
205+
206+
// Parse a word32 (big endian).
207+
let readUInt32be =
208+
parseMidi {
209+
let! a = readByte
210+
let! b = readByte
211+
let! c = readByte
212+
let! d = readByte
213+
return (word32be a b c d)
214+
}

src/ZMidi/Internal/Utils.fs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@ module Utils =
55
open System.IO
66

77
/// Builds a Word16 (big endian).
8-
let uint16be (a : byte) (b : byte) : uint16 =
8+
let word16be (a : byte) (b : byte) : uint16 =
99
let a = uint16 a
1010
let b = uint16 b
1111
(a <<< 8) + b
1212

1313
/// Builds a Word14 (big endian).
14-
let word14be a b =
15-
let a = uint16 a
16-
let b = uint16 b
17-
word14((a <<< 7) + b)
18-
14+
let word14be (a: byte) (b: byte) : word14 =
15+
let a = uint16 a
16+
let b = uint16 b
17+
word14((a <<< 7) + b)
18+
19+
let word32be (a: byte) (b: byte) (c: byte) (d: byte) : uint32 =
20+
((uint32 a) <<< 24)
21+
+ ((uint32 b) <<< 16)
22+
+ ((uint32 c) <<< 8)
23+
+ (uint32 d)
24+

src/ZMidi/Read.fs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,70 @@
11
namespace ZMidi
2+
3+
open ZMidi.DataTypes
4+
25
module ReadFile =
36
//let readMidi filename =
47
//let midiFile : Parser = ()
5-
module P = ZMidi.Internal.ParserMonad
8+
open ZMidi.Internal.ParserMonad
69
open ZMidi.Internal.Utils
10+
11+
12+
/// Apply parse then apply the check, if the check fails report
13+
/// the error message.
14+
let postCheck parser isOutputValid errorMessage =
15+
parseMidi {
16+
let! answer = parser
17+
if isOutputValid answer then
18+
return answer
19+
else
20+
return! fatalError errorMessage
21+
}
22+
23+
let inline (|TestBit|_|) (bit: int) (i: uint16) =
24+
let mask = uint16(1 <<< bit)
25+
if mask &&& i = mask then Some () else None
26+
let inline clearBit (bit: int) (i:uint16) =
27+
let mask = ~~~ (uint16(1 <<< bit))
28+
i &&& mask
29+
let assertString (s: string) =
30+
postCheck (readString s.Length) ((=) s) (Other (sprintf "assertString: expected '%s'" s))
731

32+
let assertWord32 i =
33+
postCheck readUInt32be ((=) i) (Other (sprintf "assertWord32: expected '%i'" i))
34+
35+
let fileFormat =
36+
parseMidi {
37+
match! readUInt16be with
38+
| 0us -> return MidiFormat0
39+
| 1us -> return MidiFormat1
40+
| 2us -> return MidiFormat2
41+
| x -> return! (fatalError (Other (sprintf "fileFormat: Unrecognized file format %i" x)))
42+
}
43+
let timeDivision =
44+
parseMidi {
45+
match! readUInt16be with
46+
| TestBit 15 as x -> return FramePerSecond (clearBit 15 x)
47+
| x -> return TicksPerBeat x
48+
}
49+
let header =
50+
parseMidi {
51+
let! _ = assertString "MThd"
52+
let! _ = assertWord32 6u
53+
let! format = fileFormat
54+
let! trackCount = readUInt16be
55+
let! timeDivision = timeDivision
56+
return { trackCount = trackCount
57+
timeDivision = timeDivision
58+
format = format }
59+
}
60+
//let midiFile =
61+
// parseMidi {
62+
// let! header = P.header
63+
//
64+
// }
65+
66+
//let readMidi filename =
67+
// ParserMonad.runParseMidi
868
//let pitchBend ch = "pitch bend" <??> (PitchBend ch) <$> P.readWord14be
969

1070

src/ZMidi/zmidi-fs-core.fsproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
<OutputPath>..\..\build\$(Configuration)\$(Platform)</OutputPath>
55
</PropertyGroup>
66
<ItemGroup>
7+
<EmbeddedResource Remove="obj\**" />
8+
</ItemGroup>
9+
<ItemGroup>
10+
<Compile Include="DataTypes.fs" />
711
<Compile Include="Internal\Utils.fs" />
812
<Compile Include="Internal\ParserMonad.fs" />
913
<Compile Include="Read.fs" />

0 commit comments

Comments
 (0)