This is my own library to parse MIDI files using Kotlin language/environment.
This is in development and should help me get binary information that lies in MIDI files and, for example, parse then to other formats.
This project is being built using some oline resources:
- A tutorial about MIDI specification;
- Standard MIDI file format;
- Implementing a MIDI player in Kotlin from scratch (doesn't contains raw bytes reading implementation, but contains some insights).
Currently is in a rough development phase but should be fine to use in some pushes. But if you say... I don care. Let me use as it is you can grab this project directly from its GitHub page with Source Dependencies, from Gradle tool. First, add this to your settings.gradle.kts
:
sourceControl {
gitRepository(java.net.URI("https://github.com/LucasAlfare/FLMidi")) {
producesModule("com.lucasalfare.flmidi:FLMidi")
}
}
After, add this to your build.gradle.kts
to target the master
branch of this repository:
implementation("com.lucasalfare.flmidi:FLMidi") {
version {
branch = "master"
}
}
This library has been built using the concept that MIDI files are composed by Events
. In a MIDI file we can find three categories of events: MetaEvents
ControlEvents
and SystemExclusiveEvents
. All these events categories will always contain the following information:
deltaTime
: indicates the current time diff that this event occuried;data
: the actual data associated tho this event.
All events have their own possible events and each event has its own data. The meaning behind each data value can be found in the MIDI format file specification.
Knowing this, this library is able to parse all those information to Kotlin code, and it exposes it to be used. For example, to check how many Tracks
a MIDI file contains, we can run:
import com.lucasalfare.flmidi.loadAndReadMidiFile
fun main() {
val myMidiInfo = loadAndReadMidiFile(
"path/to/my/great/midi/file.mid"
)
println(midiInfo.header.numTracks)
}
Note that this root reading function returns a MidiInfo
object, that contains other useful fields. For example, to check how many meta events are contained in a track, you can do:
import com.lucasalfare.flmidi.loadAndReadMidiFile
fun main() {
val myMidiInfo = loadAndReadMidiFile(
"path/to/my/great/midi/file.mid"
)
println(
midiInfo
.header
.tracks
.first()
.events
.filter {
it.category == EventCategory.Meta
}
.size
)
}
MIDI files stores music in a different way of, e.g., MP3 and WAVE sound formats. Once mp3 and wave sotres in their bytes informations about sound waves (here doesn't matter compression terms) using its physical theory, MIDI files stores sound in a "digital" approach.
For example, in MIDI files we can find information about the music tempo, note that was played or released or even meta informations, such as track names and copyright notices. For this reason, MIDI files are very good to help to deal with contexts that requires manipulation of each individual element from a song/sound.
In terms of bytes structure, the MIDI files basically are composed by "Chunks" of data. Thinking the structure as something like a ".xml" or ".json" hierarchy tree we can visualize a general structure like the following:
"midi": {
"header": {
"signature": "MTdH",
"length": 6,
"format": 0,
"numTracks": 1,
"timeDivision": 96
}
"track": {
"signature": "MtrK",
"numBytes": "x bytes in numeric type",
"events": [
"MetaEvent": {
"deltaTime": 0,
"trackName": "This is the name of this track!"
}
]
}
}
As you can see, the structure itself is very simple. However, is important to know some details in order to understand those details:
- In only bytes-reading context, we must to know the right rules to be used in order to read the properly amount of bytes of each information. We must, also, know if, depending the case, we are facing a byte that must be read in a real particular way;
- In terms of understanding what was actually read we need to understand what the real meaning of each read value and how it can affects the sound itself. Normally this is used when we must to interpret and "play" the MIDI file, instead of only reading its bytes.
The most important part that can be extracted from MIDI files bytes are the Events. Events, can basically be of three types: MetaEvent
, MidiEvent
and SystemExclusiveEvent
:
MetaEvents
express information that is not fundamental to define how the sound actually is, however they express some useful data to properly make the sound/track work in the same behaviour of how it was recorded to the file. For example, the information abouttempo
is stored as anMetaEvent
;
[TODO more explanations]