Skip to content

Commit

Permalink
Add some new functions that will alter "meta event" deps (deprecated …
Browse files Browse the repository at this point in the history
…them too)

After replacing current SysEx8 hacks for meta event alternatives with the
latest Flex Data messages, it will become impossible (or non-intuitive)
to maintain meta event compatibility across different MIDI versions.

So we will be removing those "too generic" common functions and introducing
more specific functions. In the meantime, deprecate handful of methods.
  • Loading branch information
atsushieno committed Jun 14, 2023
1 parent 9276854 commit 732827e
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 13 deletions.
32 changes: 23 additions & 9 deletions ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Music.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ class Midi2Music {
internal class UmpDeltaTimeComputer: DeltaTimeComputer<Ump>() {
override fun messageToDeltaTime(message: Ump) = if (message.isJRTimestamp) message.jrTimestamp else 0

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
override fun isMetaEventMessage(message: Ump, metaType: Int) = Midi2Music.isMetaEventMessage(message, metaType)
override fun isTempoMessage(message: Ump) = message.isTempo

// 3 bytes in Sysex8 pseudo meta message
override fun getTempoValue(message: Ump) = Midi2Music.getTempoValue(message)
Expand All @@ -15,8 +17,11 @@ class Midi2Music {
companion object {
private val calc = UmpDeltaTimeComputer()

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
fun getMetaEventsOfType(messages: Iterable<Ump>, metaType: Int) =
calc.getMetaEventsOfType(messages, metaType)
fun filterEvents(messages: Iterable<Ump>, filter: (Ump) -> Boolean) =
calc.filterEvents(messages, filter)

fun getTotalPlayTimeMilliseconds(messages: Iterable<Ump>, deltaTimeSpec: Int) =
if (deltaTimeSpec > 0)
Expand All @@ -38,7 +43,7 @@ class Midi2Music {
fun isMetaEventMessageStarter(message: Ump) =
message.messageType == MidiMessageType.SYSEX8_MDS &&
when (message.statusCode) {
Midi2BinaryChunkStatus.SYSEX_IN_ONE_UMP, Midi2BinaryChunkStatus.SYSEX_START ->
Midi2BinaryChunkStatus.COMPLETE_PACKET, Midi2BinaryChunkStatus.START ->
message.int1 % 0x100 == 0 &&
message.int2 shr 8 == 0 &&
message.int2 and 0xFF == 0xFF &&
Expand All @@ -47,12 +52,15 @@ class Midi2Music {
}

// returns meta event type if and only if the argument message is a META event within our own specification.
@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
fun getMetaEventType(message: Ump) : Int = if (isMetaEventMessageStarter(message)) message.int3 shr 8 % 0x100 else 0

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
fun isMetaEventMessage(message: Ump, metaType: Int) = isMetaEventMessageStarter(message) && ((message.int3 and 0xFF00) shr 8) == metaType
fun isTempoMessage(message: Ump) = calc.isTempoMessage(message)

fun getTempoValue(message: Ump) =
if (isMetaEventMessage(message, MidiMetaType.TEMPO))
if (isTempoMessage(message))
((message.int3 and 0xFF) shl 16) + ((message.int4 shr 16) and 0xFFFF)
else throw IllegalArgumentException("Attempt to calculate tempo from non-meta UMP")
}
Expand All @@ -71,11 +79,17 @@ class Midi2Music {
this.tracks.add(track)
}

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
fun getMetaEventsOfType(metaType: Int): Iterable<Pair<Int,Ump>> {
if (tracks.size > 1)
return mergeTracks().getMetaEventsOfType(metaType)
return getMetaEventsOfType(tracks[0].messages, metaType).asIterable()
}
fun filterEvents(filter: (Ump) -> Boolean): Iterable<Timed<Ump>> {
if (tracks.size > 1)
return mergeTracks().filterEvents(filter)
return filterEvents(tracks[0].messages, filter).asIterable()
}

fun getTotalTicks(): Int {
if (format != 0.toByte())
Expand Down Expand Up @@ -166,7 +180,7 @@ internal class Midi2TrackMerger(private var source: Midi2Music) {
if (i + 1 < l.size) {
val delta = l[i + 1].first - l[i].first
if (delta > 0)
l3.addAll(UmpFactory.jrTimestamps(l[i].second.group, delta.toLong()).map { v -> Ump(v) })
l3.addAll(UmpFactory.jrTimestamps(delta.toLong()).map { v -> Ump(v) })
timeAbs += delta
}
l3.add(l[i].second)
Expand Down Expand Up @@ -201,7 +215,7 @@ open class Midi2TrackSplitter(private val source: MutableList<Ump>) {
fun addMessage(timestampInsertAt: Int, e: Ump) {
if (currentTimestamp != timestampInsertAt) {
val delta = timestampInsertAt - currentTimestamp
track.messages.addAll(UmpFactory.jrTimestamps(e.group, delta.toLong()).map { i -> Ump(i)})
track.messages.addAll(UmpFactory.jrTimestamps(delta.toLong()).map { i -> Ump(i)})
}
track.messages.add(e)
currentTimestamp = timestampInsertAt
Expand Down Expand Up @@ -260,7 +274,7 @@ internal class Midi2DeltaTimeConverter internal constructor(private val source:
val dtc = Midi2Music.UmpDeltaTimeComputer()

// first, get all meta events to detect tempo changes for later uses.
val allTempoChanges = source.getMetaEventsOfType(MidiMetaType.TEMPO.toInt()).toList()
val allTempoChanges = source.filterEvents { it.isTempo }.toList()

for (srcTrack in source.tracks) {
val dstTrack = Midi2Track()
Expand All @@ -277,17 +291,17 @@ internal class Midi2DeltaTimeConverter internal constructor(private val source:
// taking those tempo changes into consideration.
var remainingTicks = ump.jrTimestamp
var durationMs = 0.0
while (nextTempoChangeIndex < allTempoChanges.size && allTempoChanges[nextTempoChangeIndex].first < currentTicks + remainingTicks) {
val ticksUntilNextTempoChange = allTempoChanges[nextTempoChangeIndex].first - currentTicks
while (nextTempoChangeIndex < allTempoChanges.size && allTempoChanges[nextTempoChangeIndex].duration.value < currentTicks + remainingTicks) {
val ticksUntilNextTempoChange = allTempoChanges[nextTempoChangeIndex].duration.value - currentTicks
durationMs += getContextDeltaTimeInMilliseconds(ticksUntilNextTempoChange, currentTempo, source.deltaTimeSpec)
currentTempo = dtc.getTempoValue(allTempoChanges[nextTempoChangeIndex].second)
currentTempo = dtc.getTempoValue(allTempoChanges[nextTempoChangeIndex].value)
nextTempoChangeIndex++
remainingTicks -= ticksUntilNextTempoChange
currentTicks += ticksUntilNextTempoChange
}
durationMs += getContextDeltaTimeInMilliseconds(remainingTicks, currentTempo, source.deltaTimeSpec)
currentTicks += remainingTicks
dstTrack.messages.addAll(UmpFactory.jrTimestamps(ump.group, durationMs / 1000.0).map { i -> Ump(i)})
dstTrack.messages.addAll(UmpFactory.jrTimestamps(durationMs / 1000.0).map { i -> Ump(i)})
}
else
dstTrack.messages.add(ump)
Expand Down
13 changes: 13 additions & 0 deletions ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/MidiMusic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ class MidiMusic {
internal class SmfDeltaTimeComputer: DeltaTimeComputer<MidiMessage>() {
override fun messageToDeltaTime(message: MidiMessage) = message.deltaTime

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
override fun isMetaEventMessage(message: MidiMessage, metaType: Int) =
message.event.eventType.toUnsigned() == META_EVENT && message.event.msb.toInt() == metaType

override fun isTempoMessage(message: MidiMessage) =
message.event.eventType.toUnsigned() == META_EVENT && message.event.msb.toInt() == MidiMetaType.TEMPO

override fun getTempoValue(message: MidiMessage) = getSmfTempo(message.event.extraData!!, message.event.extraDataOffset)
}

Expand Down Expand Up @@ -53,8 +57,11 @@ class MidiMusic {

private val calc = SmfDeltaTimeComputer()

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
fun getMetaEventsOfType(messages: Iterable<MidiMessage>, metaType: Int)
= calc.getMetaEventsOfType(messages, metaType).map { p -> MidiMessage(p.first, p.second.event) }
fun filterEvents(messages: Iterable<MidiMessage>, filter: (MidiMessage) -> Boolean) =
calc.filterEvents(messages, filter).map { p -> MidiMessage(p.duration.value, p.value.event) }

fun getTotalPlayTimeMilliseconds(messages: Iterable<MidiMessage>, deltaTimeSpec: Int) = calc.getTotalPlayTimeMilliseconds(messages, deltaTimeSpec)

Expand All @@ -71,11 +78,17 @@ class MidiMusic {
this.tracks.add(track)
}

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
fun getMetaEventsOfType(metaType: Int): Iterable<MidiMessage> {
if (format != 0.toByte())
return mergeTracks().getMetaEventsOfType(metaType)
return getMetaEventsOfType(tracks[0].messages, metaType).asIterable()
}
fun filterEvents(filter: (MidiMessage) -> Boolean): Iterable<MidiMessage> {
if (format != 0.toByte())
return mergeTracks().filterEvents(filter)
return filterEvents(tracks[0].messages, filter).asIterable()
}

fun getTotalTicks(): Int {
if (format != 0.toByte())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,35 @@ internal fun Byte.toUnsigned() = if (this < 0) 0x100 + this else this.toInt()
internal fun Short.toUnsigned() = if (this < 0) 0x10000 + this else this.toInt()
internal fun Int.toUnsigned() = if (this < 0) 0x100000000 + this else this.toLong()

// DeltaClockstamp time unit
data class Dc(val value: Int)

val Int.dc
get() = Dc(this)

data class Timed<T>(val duration: Dc, val value: T)

internal abstract class DeltaTimeComputer<T> {

abstract fun messageToDeltaTime(message: T) : Int

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
abstract fun isMetaEventMessage(message: T, metaType: Int) : Boolean

abstract fun isTempoMessage(message: T): Boolean

abstract fun getTempoValue(message: T) : Int

fun filterEvents(messages: Iterable<T>, filter: (T) -> Boolean) : Sequence<Timed<T>> = sequence {
var v = 0
for (m in messages) {
v += messageToDeltaTime(m)
if (filter(m))
yield(Timed(v.dc, m))
}
}

@Deprecated("It is going to be impossible to support in SMF2 so we will remove it")
fun getMetaEventsOfType(messages: Iterable<T>, metaType: Int) : Sequence<Pair<Int,T>> = sequence {
var v = 0
for (m in messages) {
Expand Down Expand Up @@ -39,7 +60,7 @@ internal abstract class DeltaTimeComputer<T> {
if (deltaTime != messageDeltaTime)
break
t += messageDeltaTime
if (isMetaEventMessage(m, MidiMetaType.TEMPO))
if (isTempoMessage(m))
tempo = getTempoValue(m)
}
return v.toInt()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ interface MidiPlayerTimer {

@OptIn(ExperimentalTime::class)
class SimpleAdjustingMidiPlayerTimer(private val timeSource: TimeSource = TimeSource.Monotonic) : MidiPlayerTimer {
@Deprecated("This constructor will be removed in the future")
constructor() : this(TimeSource.Monotonic)

private lateinit var startedTime: TimeMark
private var nominalTotalSeconds: Double = 0.0

Expand Down

0 comments on commit 732827e

Please sign in to comment.