Skip to content

Commit 9bdce5a

Browse files
committed
Added converter for delta time to real time
1 parent c5a3c4e commit 9bdce5a

File tree

6 files changed

+89
-59
lines changed

6 files changed

+89
-59
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package pl.lemanski.mikroSoundFont.io.midi
22

3+
import pl.lemanski.mikroSoundFont.midi.MidiMessage
4+
import pl.lemanski.mikroSoundFont.midi.MidiMetaMessage
35
import pl.lemanski.mikroSoundFont.midi.MidiTrack
6+
import pl.lemanski.mikroSoundFont.midi.UnsupportedMidiMessage
47

58
data class MidiFile(
69
val header: Header,
@@ -10,4 +13,36 @@ data class MidiFile(
1013
val trackCount: Int,
1114
val division: Int,
1215
)
16+
17+
fun getMessagesOverTime(): List<MidiMessage> {
18+
val division = header.division
19+
var ticks2time = 500000 / (1000.0 * division)
20+
var tempoTicks = 0 // Tick count at the last tempo change
21+
var tempoMsec = 0 // Milliseconds at the last tempo change
22+
23+
val allMessages = mutableListOf<MidiMessage>()
24+
25+
for (track in tracks) {
26+
var trackTicks = 0
27+
28+
for (msg in track.messages) {
29+
if (msg is UnsupportedMidiMessage) continue
30+
trackTicks += msg.time
31+
32+
val millis = tempoMsec + ((trackTicks - tempoTicks) * ticks2time).toInt()
33+
msg.time = millis
34+
35+
if (msg is MidiMetaMessage.SetTempo) {
36+
val tempo = msg.tempo
37+
ticks2time = tempo / (1000.0 * division)
38+
tempoTicks = trackTicks
39+
tempoMsec = millis
40+
}
41+
42+
allMessages.add(msg)
43+
}
44+
}
45+
46+
return allMessages.sortedBy { it.time }
47+
}
1348
}

core/src/commonMain/kotlin/pl/lemanski/mikroSoundFont/io/midi/message/read/MidiMetaMessageParser.kt

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,21 @@ internal class MidiMetaMessageParser(
4242
)
4343
}
4444

45-
private fun parseSetTempo(deltaTime: Int, dataSize: Int): MidiMessage = skipMessage(dataSize)
46-
// {
47-
// if (dataSize != 3) {
48-
// throw InvalidMidiDataException("Malformed MIDI. Set Tempo message must be 3 bytes long.")
49-
// }
50-
//
51-
// var tempoValue = 0
52-
// repeat(dataSize) {
53-
// buffer.readByte()
54-
// tempoValue = tempoValue shl 8 or buffer.readByte().toInt()
55-
// }
56-
//
57-
// val bpm = 60_000_000 / tempoValue // 1 minute in microseconds / tempoValue
58-
//
59-
// return MidiMetaMessage.SetTempo(
60-
// time = deltaTime,
61-
// bpm = bpm
62-
// )
63-
// }
45+
private fun parseSetTempo(deltaTime: Int, dataSize: Int): MidiMetaMessage.SetTempo {
46+
if (dataSize != 3) {
47+
throw InvalidMidiDataException("Malformed MIDI. Set Tempo message must be 3 bytes long.")
48+
}
49+
50+
var tempoValue = 0
51+
repeat(dataSize) {
52+
tempoValue = tempoValue shl 8 or buffer.readByte().toInt()
53+
}
54+
55+
return MidiMetaMessage.SetTempo(
56+
time = deltaTime,
57+
tempo = tempoValue
58+
)
59+
}
6460

6561
private fun skipMessage(dataSize: Int): MidiMessage {
6662
buffer.skip(dataSize.toLong())

core/src/commonMain/kotlin/pl/lemanski/mikroSoundFont/midi/MidiMessage.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@ package pl.lemanski.mikroSoundFont.midi
4141
*/
4242
sealed interface MidiMessage {
4343
val type: MidiMessageType
44-
val time: Int
44+
var time: Int
4545
}
4646

4747
sealed class MidiSystemMessage : MidiMessage {
4848
data object Sysex : MidiSystemMessage() {
4949
override val type: MidiMessageType = MidiMessageType.SYS_EX
50-
override val time: Int = 0
50+
override var time: Int = 0
5151
}
5252

5353
data object Eox : MidiSystemMessage() {
5454
override val type: MidiMessageType = MidiMessageType.EOX
55-
override val time: Int = 0
55+
override var time: Int = 0
5656
}
5757

5858
// other messages
@@ -82,22 +82,22 @@ sealed class MidiSystemMessage : MidiMessage {
8282

8383
sealed class MidiMetaMessage : MidiMessage {
8484
data class SetTempo(
85-
override val time: Int,
86-
val bpm: Int,
85+
override var time: Int,
86+
val tempo: Int,
8787
) : MidiMetaMessage() {
8888
override val type: MidiMessageType = MidiMessageType.SET_TEMPO
8989
}
9090

9191
data class EndOfTrack(
92-
override val time: Int
92+
override var time: Int
9393
) : MidiMetaMessage() {
9494
override val type: MidiMessageType = MidiMessageType.END_OF_TRACK
9595
}
9696
}
9797

9898
sealed class MidiVoiceMessage : MidiMessage {
9999
data class NoteOn(
100-
override val time: Int,
100+
override var time: Int,
101101
val channel: Int,
102102
val key: Int,
103103
val velocity: Int,
@@ -106,7 +106,7 @@ sealed class MidiVoiceMessage : MidiMessage {
106106
}
107107

108108
data class NoteOff(
109-
override val time: Int,
109+
override var time: Int,
110110
val channel: Int,
111111
val key: Int,
112112
val velocity: Int,
@@ -115,7 +115,7 @@ sealed class MidiVoiceMessage : MidiMessage {
115115
}
116116

117117
data class KeyPressure(
118-
override val time: Int,
118+
override var time: Int,
119119
val channel: Int,
120120
val key: Int,
121121
val keyPressure: Int,
@@ -124,7 +124,7 @@ sealed class MidiVoiceMessage : MidiMessage {
124124
}
125125

126126
data class ControlChange(
127-
override val time: Int,
127+
override var time: Int,
128128
val channel: Int,
129129
val control: Int,
130130
val controlValue: Int,
@@ -133,23 +133,23 @@ sealed class MidiVoiceMessage : MidiMessage {
133133
}
134134

135135
data class ProgramChange(
136-
override val time: Int,
136+
override var time: Int,
137137
val channel: Int,
138138
val program: Int,
139139
) : MidiVoiceMessage() {
140140
override val type: MidiMessageType = MidiMessageType.PROGRAM_CHANGE
141141
}
142142

143143
data class ChannelPressure(
144-
override val time: Int,
144+
override var time: Int,
145145
val channel: Int,
146146
val channelPressure: Int,
147147
) : MidiVoiceMessage() {
148148
override val type: MidiMessageType = MidiMessageType.CHANNEL_PRESSURE
149149
}
150150

151151
data class PitchBend(
152-
override val time: Int,
152+
override var time: Int,
153153
val channel: Int,
154154
val pitchBend: Int,
155155
) : MidiVoiceMessage() {
@@ -159,5 +159,5 @@ sealed class MidiVoiceMessage : MidiMessage {
159159

160160
data object UnsupportedMidiMessage : MidiMessage {
161161
override val type: MidiMessageType = MidiMessageType.UNSUPPORTED
162-
override val time: Int = 0
162+
override var time: Int = 0
163163
}

core/src/commonMain/kotlin/pl/lemanski/mikroSoundFont/midi/MidiSequencer.kt

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import pl.lemanski.mikroSoundFont.getLogger
66
class MidiSequencer(
77
private val soundFont: SoundFont,
88
private val sampleRate: Int,
9-
private val channelCount: Int,
109
private val sampleBlockSize: Int
1110
) {
1211
private val logger = getLogger()
@@ -39,19 +38,9 @@ class MidiSequencer(
3938
return audioBuffer
4039
}
4140

42-
private fun MidiMessage.process() {
43-
type.let {
44-
when (it) {
45-
MidiMessageType.NOTE_OFF -> {
46-
(this as MidiVoiceMessage.NoteOff)
47-
soundFont.noteOff(channel, key)
48-
}
49-
MidiMessageType.NOTE_ON -> {
50-
(this as MidiVoiceMessage.NoteOff)
51-
soundFont.noteOn(channel, key, velocity / 127.0f)
52-
}
53-
else -> logger.log("Unknown message type")
54-
}
55-
}
41+
private fun MidiMessage.process() = when (this) {
42+
is MidiVoiceMessage.NoteOff -> soundFont.noteOff(channel, key)
43+
is MidiVoiceMessage.NoteOn -> soundFont.noteOn(channel, key, velocity / 127.0f)
44+
else -> logger.log("Unknown message type")
5645
}
5746
}
Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package pl.lemanski.mikroSoundFont.midi
22

3+
import pl.lemanski.mikroSoundFont.MikroSoundFont
34
import pl.lemanski.mikroSoundFont.io.loadFile
45
import pl.lemanski.mikroSoundFont.io.midi.MidiFileParser
6+
import pl.lemanski.mikroSoundFont.io.saveFile
7+
import pl.lemanski.mikroSoundFont.io.toByteArrayLittleEndian
8+
import pl.lemanski.mikroSoundFont.io.wav.WavFileHeader
9+
import pl.lemanski.mikroSoundFont.io.wav.toByteArray
510
import kotlin.test.Test
611

712
class MidiTest {
@@ -11,10 +16,17 @@ class MidiTest {
1116
fun testMidi() {
1217
val midiFileBuffer = loadFile("$dir\\venture.mid")
1318
val midiFile = MidiFileParser(midiFileBuffer).parse()
14-
midiFile.tracks.forEach {
15-
it.messages.forEach {
16-
println(it.type.name)
17-
}
18-
}
19+
val messages = midiFile.getMessagesOverTime()
20+
21+
val soundFont = MikroSoundFont.load("$dir\\font.sf2")
22+
23+
val sequencer = MidiSequencer(soundFont, 44100, 512)
24+
sequencer.loadMidiEvents(messages)
25+
val wavBytes = sequencer.generate()
26+
27+
val wavHeader = WavFileHeader.write(44_100u, wavBytes.size.toUInt(), 2u)
28+
val file = wavHeader.toByteArray() + wavBytes.toByteArrayLittleEndian()
29+
30+
saveFile(file, "$dir\\output.wav")
1931
}
2032
}
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package pl.lemanski.mikroSoundFont
22

3+
import pl.lemanski.mikroSoundFont.internal.getSoundFontDelegate
4+
35
actual object MikroSoundFont {
4-
actual fun load(path: String): SoundFont {
5-
TODO("Not yet implemented")
6-
}
6+
actual fun load(path: String): SoundFont = getSoundFontDelegate(path)
77

8-
actual fun load(memory: ByteArray): SoundFont {
9-
TODO("Not yet implemented")
10-
}
8+
actual fun load(memory: ByteArray): SoundFont = getSoundFontDelegate(memory)
119
}

0 commit comments

Comments
 (0)