Skip to content

Commit e7f4ac9

Browse files
committed
Added some work on midi parsing
1 parent d55dc88 commit e7f4ac9

File tree

11 files changed

+448
-903
lines changed

11 files changed

+448
-903
lines changed

core/src/commonMain/kotlin/pl/lemanski/mikroSoundFont/io/ByteArrayUtils.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package pl.lemanski.mikroSoundFont.io
22

3+
import kotlin.experimental.and
4+
35
/**
46
* Convert byte array to float array with little endian order
57
*/
@@ -44,5 +46,43 @@ fun FloatArray.toByteArrayBigEndian(): ByteArray {
4446

4547

4648
fun ByteArray.readShortAt(index: Int): Short {
47-
return ((this[index].toInt() and 0xFF) shl 8 or (this[index + 1].toInt() and 0xFF)).toShort()
49+
val shortBuffer = this.copyOfRange(index, index + 2).map { it.toInt() and 0xFF }
50+
return (shortBuffer[1] or (shortBuffer[0] shl 8)).toShort()
51+
}
52+
53+
fun ByteArray.readIntAt(index: Int): Int {
54+
val intBuffer = this.copyOfRange(index, index + 4).map { it.toInt() and 0xFF }
55+
return intBuffer[3] or (intBuffer[2] shl 8) or (intBuffer[1] shl 16) or (intBuffer[0] shl 24)
56+
}
57+
58+
fun ByteArray.readByteAt(index: Int): Byte {
59+
return this[index] and 0xFF.toByte()
60+
}
61+
62+
fun ByteArray.readVariableLengthAt(index: Int): Pair<Int, Int>? {
63+
var pos = index
64+
var result = 0
65+
var i = 0
66+
var countinuation: Int
67+
68+
while (i < 4) {
69+
if (pos >= size) {
70+
break
71+
}
72+
73+
countinuation = this[pos].toInt() and 0xFF
74+
pos++
75+
76+
if (countinuation and 0x80 != 0) {
77+
// store data bits in result
78+
result = ((result or (countinuation and 0x7F)) shl 7)
79+
} else {
80+
// return result
81+
return (result or countinuation) to pos
82+
}
83+
84+
i++
85+
}
86+
87+
return null
4888
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package pl.lemanski.mikroSoundFont.io.midi
2+
3+
import pl.lemanski.mikroSoundFont.midi.MidiMessage
4+
import pl.lemanski.mikroSoundFont.midi.MidiMessageType
5+
import pl.lemanski.mikroSoundFont.midi.MidiTrack
6+
7+
class MidiFile(
8+
private val buffer: ByteArray
9+
) {
10+
val header: MidiFileHeader
11+
val tracks: Array<MidiTrack>
12+
13+
init {
14+
header = MidiFileHeader.fromBytes(buffer)
15+
var data = buffer.copyOfRange(MidiFileHeader.SIZE_IN_BYTES, buffer.size)
16+
tracks = Array(header.trackCount) {
17+
val track = MidiTrack.fromBytes(data)
18+
data = data.copyOfRange(track.rawData.size + 8, data.size)
19+
track
20+
}
21+
}
22+
23+
/**
24+
* Messages are ordered by the absolute time of occurrence
25+
* meaning that delta times has been converted to absolute times.
26+
*
27+
* @return messages parsed from all tracks.
28+
*/
29+
fun messages(): List<MidiMessage> {
30+
val actualMessages = mutableListOf<MidiMessage>()
31+
var totalTicks = 0
32+
var tempoMsec = 0
33+
var tempoTicks = 0
34+
var ticksToTime = 500_000.0 / (1000.0 * header.division)
35+
36+
for (track in tracks) {
37+
track.messages.sortedBy { it.time }
38+
for (msg in track.messages) {
39+
totalTicks += msg.time
40+
val actualTime = tempoMsec + ((totalTicks - tempoTicks) * ticksToTime).toInt()
41+
val actual = msg.copy(time = actualTime)
42+
43+
if (actual.type is MidiMessageType.SetTempo) {
44+
val tempo = actual.type.tempo
45+
ticksToTime = (tempo[0].toInt() shl 16 or (tempo[1].toInt() shl 8) or tempo[2].toInt()) / (1000.0 * header.division)
46+
tempoMsec = actualTime
47+
tempoTicks = totalTicks
48+
}
49+
50+
actualMessages.add(actual)
51+
}
52+
}
53+
54+
return actualMessages
55+
}
56+
}

core/src/commonMain/kotlin/pl/lemanski/mikroSoundFont/io/midi/MidiFileHeader.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ import pl.lemanski.mikroSoundFont.io.readShortAt
55

66
data class MidiFileHeader(
77
val trackCount: Int,
8-
val division: Int
8+
val division: Int,
99
) {
1010
companion object {
11+
const val SIZE_IN_BYTES = 14
12+
1113
@OptIn(ExperimentalStdlibApi::class)
1214
fun fromBytes(buffer: ByteArray): MidiFileHeader {
1315
val trackCount: Int
1416
val division: Int
1517

16-
if (buffer.size < 14) {
18+
if (buffer.size < SIZE_IN_BYTES) {
1719
throw InvalidMidiDataException("Unexpected buffer size. Buffer is too small.")
1820
}
1921

20-
val headerBytes = buffer.copyOfRange(0, 14)
22+
val headerBytes = buffer.copyOfRange(0, SIZE_IN_BYTES)
2123

2224
if (headerBytes.copyOfRange(0, 4).decodeToString() != "MThd" || headerBytes[7] != 6.toByte() || headerBytes[9] > 2) {
2325
throw InvalidMidiDataException("Invalid MThd header: ${headerBytes.toHexString()}")
@@ -30,7 +32,7 @@ data class MidiFileHeader(
3032
trackCount = headerBytes.readShortAt(10).toInt()
3133
division = headerBytes.readShortAt(12).toInt() // ticks per beat
3234

33-
if (trackCount <= 0 ) {
35+
if (trackCount <= 0) {
3436
throw InvalidMidiDataException("Invalid track values: $trackCount")
3537
}
3638

@@ -44,4 +46,4 @@ data class MidiFileHeader(
4446
)
4547
}
4648
}
47-
}
49+
}

core/src/commonMain/kotlin/pl/lemanski/mikroSoundFont/io/midi/MidiFileParser.kt

Lines changed: 0 additions & 194 deletions
This file was deleted.

0 commit comments

Comments
 (0)