Last active
September 6, 2022 19:28
-
-
Save LucasAlfare/e55a78f33d5919402b18baa44cf0add1 to your computer and use it in GitHub Desktop.
Helper that I created in order to extract better information from MIDI files about notes, chords and rests (and their durations). I should include timming convertions in the future, btw feel free to help improve.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author Francisco Lucas (a.k.a. LucasAlfare); 06/09/2022. | |
*/ | |
import java.io.File | |
import javax.sound.midi.MidiSystem | |
import javax.sound.midi.ShortMessage | |
fun main() { | |
processedNotesFrom("test.mid") | |
.forEach { | |
println(it) | |
} | |
} | |
/** | |
* This function takes a MIDI file and parses its info into a list of | |
* [Element]. [Element] is an entity that can represent a Note, a Chord | |
* or a Rest. | |
* | |
* This is made in order to keep the info easy to understand, making possible | |
* to know what is each king of separated element in the MIDI structure. | |
* | |
* @param midiFilePathname the path from a valid MIDI file, in [String] format. | |
* | |
* @return a list of [Element] items, which could be [Note], [Chord] or [Rest]. | |
*/ | |
fun processedNotesFrom(midiFilePathname: String): MutableList<Any> { | |
val notes = mutableListOf<Any>() | |
val rests = mutableListOf<Any>() | |
val chords = mutableListOf<Any>() | |
val final = mutableListOf<Any>() | |
fun buildNotesFromFile() { | |
// loads a MIDI file into a sequence object | |
val sequence = MidiSystem.getSequence(File(midiFilePathname)) | |
val tmpNotesMap = mutableMapOf<Int, Any>() | |
// iterates over the tracks of the loaded sequence | |
sequence.tracks.forEach { track -> | |
// iterates over events of the current track | |
for (i in 0 until track.size()) { | |
val event = track.get(i) | |
val message = event.message | |
if (message is ShortMessage) { | |
when (message.command) { | |
ShortMessage.NOTE_ON -> { | |
val currentKey = message.data1 | |
tmpNotesMap[currentKey] = Note(key = currentKey, on = event.tick) | |
} | |
ShortMessage.NOTE_OFF -> { | |
val currentKey = message.data1 | |
val tmpNote = tmpNotesMap[currentKey] as Note | |
tmpNote.off = event.tick | |
notes += tmpNote | |
} | |
else -> {} | |
} | |
} | |
} | |
} | |
} | |
fun buildRests() { | |
var lastOffTick = 0L | |
notes.forEach { | |
(it as Note) // exposing expected type in order force smart cast | |
if (it.on > lastOffTick) { | |
rests += Rest(lastOffTick, it.on) | |
} | |
lastOffTick = it.off | |
} | |
} | |
fun buildChords() { | |
var lastOnTick = 0L | |
var lastOffTick = 0L | |
notes.forEachIndexed { index, element -> | |
(element as Note) // exposing expected type in order force smart cast | |
if (element.on == lastOnTick && element.off == lastOffTick) { | |
@Suppress("UNCHECKED_CAST") | |
chords += Chord( | |
listOf(element, notes[index - 1]) as MutableList<Note> | |
) | |
} | |
lastOnTick = element.on | |
lastOffTick = element.off | |
} | |
} | |
fun buildFinal() { | |
val notesRestsChords = mutableListOf<Any>() | |
notesRestsChords.addAll(notes) | |
notesRestsChords.addAll(rests) | |
notesRestsChords.addAll(chords) | |
notesRestsChords.sortBy { | |
when (it) { | |
is Note -> it.on | |
is Rest -> it.on | |
else -> (it as Chord).on | |
} | |
} | |
notesRestsChords.forEach { | |
if (it is Chord) { | |
final.removeLast() | |
final.removeLast() | |
} | |
final += it | |
} | |
} | |
buildNotesFromFile() | |
buildRests() | |
buildChords() | |
buildFinal() | |
return final | |
} | |
class Note(var key: Int = 0, var on: Long = 0, var off: Long = 0) { | |
// just for convenience | |
private val names = arrayOf("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") | |
private val octave = (key / 12) - 1 | |
override fun toString(): String { | |
return "Note(name=${names[key % 12]}${octave}, on=$on, off=$off)" | |
} | |
} | |
class Rest(var on: Long = 0, var off: Long = 0) { | |
override fun toString() = "Rest(on=$on, off=$off)" | |
} | |
@Suppress("MemberVisibilityCanBePrivate") | |
class Chord( | |
val notes: MutableList<Note> = mutableListOf(), | |
var on: Long = notes.first().on, | |
var off: Long = notes.first().off | |
) { | |
init { | |
notes.sortBy { it.key } | |
} | |
override fun toString() = "Chord(notes=$notes, on=$on, off=$off)" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment