Skip to content

Commit 815affd

Browse files
authored
Merge pull request #225 from orchetect/smf-custom-chunk-hotfix
SMF Parse Custom Chunk Fix
2 parents dbd1b85 + ddb885a commit 815affd

File tree

5 files changed

+84
-16
lines changed

5 files changed

+84
-16
lines changed

Sources/MIDIKitSMF/MIDIFile/MIDIFile decode.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,7 @@ extension MIDIFile {
8686
// as per Standard MIDI File 1.0 Spec:
8787
// unrecognized chunks should be skipped and not throw an error
8888

89-
let newUnrecognizedChunk = try Chunk.UnrecognizedChunk(
90-
midi1SMFRawBytesStream: chunkData.bytes
91-
)
89+
let newUnrecognizedChunk = Chunk.UnrecognizedChunk(id: chunkTypeString, rawData: chunkData)
9290
newChunk = .other(newUnrecognizedChunk)
9391
}
9492
} catch let error as DecodeError {

Sources/MIDIKitSMF/Protocols/MIDIFileChunk.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
public protocol MIDIFileChunk: Sendable {
88
/// 4-character ASCII string identifying the chunk.
99
///
10-
/// For standard MIDI tracks, this is MTrk.
11-
/// For non-track chunks, any 4-character identifier can be used except for "MTrk".
10+
/// For standard MIDI tracks, this is `MTrk`.
11+
/// For non-track chunks, any 4-character identifier can be used except for `MTrk` or `MThd`.
1212
var identifier: String { get }
1313

1414
// TODO: add init from raw data, passing in midi header timing info etc.

Tests/MIDIKitSMFTests/MIDI File Tests Constants.swift

+40-7
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ enum kMIDIFile {
1212
// swiftformat:options --maxwidth none
1313

1414
/// Example Digital Performer 8 MIDI file export containing marker and cue point events.
15-
static let DP8Markers: [UInt8] = [
16-
0x4D, 0x54, 0x68, 0x64, // MThd
15+
static let dp8Markers: [UInt8] = [
16+
0x4D, 0x54, 0x68, 0x64, // "MThd"
1717
0x00, 0x00, 0x00, 0x06, // 6 header bytes
1818
0x00, 0x01, // type 1 file
19-
0x00, 0x03, // 3 tracks
19+
0x00, 0x03, // 3 chunks
2020
0x01, 0xE0, // musical divisions, 480 ticks per quarter
2121

2222
// track 1
23-
0x4D, 0x54, 0x72, 0x6B, // MTrk
24-
0x00, 0x00, 0x00, 0xBE, // track length
23+
0x4D, 0x54, 0x72, 0x6B, // "MTrk"
24+
0x00, 0x00, 0x00, 0xBE, // track length (int 190)
2525
0x00, 0xFF, 0x03, 0x05, 0x53, 0x65, 0x71, 0x2D, 0x31, // track name
2626
0x00, 0xFF, 0x54, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, // SMPTE offset
2727
0x00, 0xFF, 0x58, 0x04, 0x04, 0x02, 0x18, 0x08, // time signature
@@ -55,18 +55,51 @@ enum kMIDIFile {
5555
0x00, 0xFF, 0x2F, 0x00, // end of track
5656

5757
// track 2
58-
0x4D, 0x54, 0x72, 0x6B, // MTrk
58+
0x4D, 0x54, 0x72, 0x6B, // "MTrk"
5959
0x00, 0x00, 0x00, 0x1A, // track length
6060
0x00, 0xFF, 0x03, 0x06, 0x4D, 0x49, 0x44, 0x49, 0x2D, 0x31, // track name
6161
0x81, 0xD3, 0x8E, 0x77, 0x90, 0x3B, 0x40, // note on
6262
0x86, 0x60, 0x80, 0x3B, 0x40, // note off
6363
0x00, 0xFF, 0x2F, 0x00, // end of track
6464

6565
// track 3
66-
0x4D, 0x54, 0x72, 0x6B, // MTrk
66+
0x4D, 0x54, 0x72, 0x6B, // "MTrk"
6767
0x00, 0x00, 0x00, 0x13, // track length
6868
0x00, 0xFF, 0x03, 0x06, 0x4D, 0x49, 0x44, 0x49, 0x2D, 0x32, // track name
6969
0x00, 0xFF, 0x20, 0x01, 0x00, // channel prefix: chan 0
7070
0x00, 0xFF, 0x2F, 0x00 // end of track
7171
]
72+
73+
/// Example MIDI file containing a custom chunk.
74+
static let customChunk: [UInt8] = [
75+
0x4D, 0x54, 0x68, 0x64, // "MThd"
76+
0x00, 0x00, 0x00, 0x06, // 6 header bytes
77+
0x00, 0x01, // type 1 file
78+
0x00, 0x03, // 2 chunks
79+
0x01, 0xE0, // musical divisions, 480 ticks per quarter
80+
81+
// chunk 1 - track
82+
0x4D, 0x54, 0x72, 0x6B, // "MTrk"
83+
0x00, 0x00, 0x00, 0x3F, // track length (int 63)
84+
0x00, 0xFF, 0x03, 0x05, 0x50, 0x69, 0x61, 0x6E, 0x6F, // track name, 5 bytes, "Piano"
85+
0x00, 0xFF, 0x04, 0x10, // instrument name, 0x10 (16) bytes, "Steinway Piano 2"
86+
0x53, 0x74, 0x65, 0x69,
87+
0x6E, 0x77, 0x61, 0x79,
88+
0x20, 0x50, 0x69, 0x61,
89+
0x6E, 0x6F, 0x20, 0x32,
90+
0x00, 0xFF, 0x58, 0x04, 0x04, 0x02, 0x18, 0x08, // time signature
91+
0x00, 0xFF, 0x59, 0x02, 0x00, 0x00, // key signature
92+
0x00, 0xFF, 0x54, 0x05, 0x60, 0x00, 0x00, 0x00, 0x00, // SMPTE offset
93+
0x00, 0xFF, 0x51, 0x03, 0x07, 0xA1, 0x1F, // tempo
94+
0x00, 0xFF, 0x2F, 0x00, // end of track
95+
96+
// chunk 2 - custom chunk
97+
0x4B, 0x64, 0x6F, 0x63, // "Kdoc"
98+
0x00, 0x00, 0x00, 0x23, // track length (35 bytes)
99+
0x0D, 0x00, 0x00, 0x80, 0x3F, 0x10, 0x01, 0x22,
100+
0x14, 0x0D, 0x00, 0x00, 0xF0, 0x41, 0x15, 0x00,
101+
0x00, 0x48, 0x42, 0x1D, 0x00, 0x00, 0xA0, 0x41,
102+
0x25, 0x00, 0x00, 0x20, 0x42, 0x30, 0x02, 0x38,
103+
0x16, 0x40, 0x5A
104+
]
72105
}

Tests/MIDIKitSMFTests/MIDI File decode Tests.swift

+39-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,46 @@
44
// © 2021-2024 Steffan Andrews • Licensed under MIT License
55
//
66

7-
@testable import MIDIKitSMF
7+
import MIDIKitSMF
88
import XCTest
99

1010
final class MIDIFileDecodeTests: XCTestCase {
11-
// empty for now
11+
// swiftformat:options --wrapcollections preserve
12+
// swiftformat:disable spaceInsideParens spaceInsideBrackets spacearoundoperators
13+
// swiftformat:options --maxwidth none
14+
15+
/// Test parsing a MIDI file that contains an unrecognized chunk.
16+
func testCustomChunk() throws {
17+
let midiFile = try MIDIFile(rawData: kMIDIFile.customChunk.data)
18+
19+
XCTAssertEqual(midiFile.chunks.count, 2)
20+
21+
// chunk 1 - track
22+
23+
guard case let .track(track1) = midiFile.chunks[0] else {
24+
XCTFail()
25+
return
26+
}
27+
28+
XCTAssertEqual(track1.events.count, 6)
29+
30+
// chunk 2 - unknown chunk
31+
32+
guard case let .other(unknownChunk) = midiFile.chunks[1] else {
33+
XCTFail()
34+
return
35+
}
36+
37+
XCTAssertEqual(unknownChunk.identifier, "Kdoc")
38+
39+
XCTAssertEqual(unknownChunk.rawData.count, 35)
40+
XCTAssertEqual(
41+
unknownChunk.rawData.bytes,
42+
[0x0D, 0x00, 0x00, 0x80, 0x3F, 0x10, 0x01, 0x22,
43+
0x14, 0x0D, 0x00, 0x00, 0xF0, 0x41, 0x15, 0x00,
44+
0x00, 0x48, 0x42, 0x1D, 0x00, 0x00, 0xA0, 0x41,
45+
0x25, 0x00, 0x00, 0x20, 0x42, 0x30, 0x02, 0x38,
46+
0x16, 0x40, 0x5A]
47+
)
48+
}
1249
}

Tests/MIDIKitSMFTests/MIDI File encode Tests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,12 @@ final class MIDIFileEncodeTests: XCTestCase {
116116

117117
// test if midiFile structs are equal by way of Equatable
118118

119-
let dp8MarkersRawData = try MIDIFile(rawData: kMIDIFile.DP8Markers.data)
119+
let dp8MarkersRawData = try MIDIFile(rawData: kMIDIFile.dp8Markers.data)
120120
XCTAssertEqual(midiFile, dp8MarkersRawData)
121121

122122
// test if raw data is equal
123123

124124
let constructedData = try midiFile.rawData()
125-
XCTAssertEqual(constructedData, kMIDIFile.DP8Markers.data)
125+
XCTAssertEqual(constructedData, kMIDIFile.dp8Markers.data)
126126
}
127127
}

0 commit comments

Comments
 (0)