Skip to content

Commit

Permalink
Implement delta clockstamp support in UmpTranslator and Midi2Player.
Browse files Browse the repository at this point in the history
  • Loading branch information
atsushieno committed Jun 18, 2023
1 parent 88a3bcc commit 49ccb32
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 12 deletions.
28 changes: 24 additions & 4 deletions ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Music.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,19 @@ internal class Midi2TrackMerger(private var source: Midi2Music) {
internal fun getMergedMessages(): Midi2Music {
var l = mutableListOf<Pair<Int,Ump>>()

var jrTimestampShowedUp = false

for (track in source.tracks) {
var absTime = 0
for (mev in track.messages) {
if (mev.isJRTimestamp)
if (mev.isDeltaClockstamp) {
if (jrTimestampShowedUp)
throw UnsupportedOperationException("The source contains both JR Timestamp and Delta Clockstamp, which is not supported.")
absTime += mev.deltaClockstamp
} else if (mev.isJRTimestamp) {
absTime += mev.jrTimestamp
jrTimestampShowedUp = true
}
else
l.add(Pair(absTime, mev))
}
Expand Down Expand Up @@ -183,8 +191,13 @@ internal class Midi2TrackMerger(private var source: Midi2Music) {
while (i < l.size) {
if (i + 1 < l.size) {
val delta = l[i + 1].first - l[i].first
if (delta > 0)
l3.add(Ump(UmpFactory.deltaClockstamp(delta)))
if (delta > 0) {
if (jrTimestampShowedUp) {
l3.addAll(UmpFactory.jrTimestamps(delta.toLong()).map { Ump(it) })
} else {
l3.add(Ump(UmpFactory.deltaClockstamp(delta)))
}
}
timeAbs += delta
}
l3.add(l[i].second)
Expand Down Expand Up @@ -245,9 +258,16 @@ open class Midi2TrackSplitter(private val source: MutableList<Ump>) {

fun split(): Midi2Music {
var totalDeltaTime = 0
var jrTimestampShowedUp = false
for (e in source) {
if (e.isJRTimestamp)
if (e.isDeltaClockstamp) {
if (jrTimestampShowedUp)
throw UnsupportedOperationException("The source contains both JR Timestamp and Delta Clockstamp, which is not supported.")
totalDeltaTime += e.deltaClockstamp
} else if (e.isJRTimestamp) {
jrTimestampShowedUp = true
totalDeltaTime += e.jrTimestamp
}
val id: Int = getTrackId(e)
getTrack(id).addMessage(totalDeltaTime, e)
}
Expand Down
16 changes: 13 additions & 3 deletions ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Player.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ internal class Midi2EventLooper(var messages: List<Ump>, private val timer: Midi
override fun getNextMessage() = messages[eventIdx]

override fun getContextDeltaTimeInSeconds(m: Ump): Double {
return if (deltaTimeSpec > 0)
if (m.isJRTimestamp) currentTempo.toDouble() / 1_000_000 * m.jrTimestamp / deltaTimeSpec / tempoRatio else 0.0
return if (deltaTimeSpec > 0) {
if (m.isDeltaClockstamp)
currentTempo.toDouble() / 1_000_000 * m.deltaClockstamp / deltaTimeSpec / tempoRatio
else if (m.isJRTimestamp)
currentTempo.toDouble() / 1_000_000 * m.jrTimestamp / deltaTimeSpec / tempoRatio
else 0.0
}
else
if (m.isJRTimestamp) m.jrTimestamp * 1.0 / 31250 else 0.0
}
Expand Down Expand Up @@ -233,7 +238,12 @@ internal class Midi2SimpleSeekProcessor(ticks: Int) : SeekProcessor<Ump> {
private var current: Int = 0

override fun filterMessage(message: Ump): SeekFilterResult {
current += if(message.isJRTimestamp) message.jrTimestamp else 0
current +=
if(message.isDeltaClockstamp)
message.deltaClockstamp
else if(message.isJRTimestamp)
message.jrTimestamp
else 0
if (current >= seekTo)
return SeekFilterResult.PASS_AND_TERMINATE
when (message.statusCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ class UmpTranslatorTest {
@Test
fun testConvertSingleUmpToMidi1 ()
{
var ump = Ump(0L)
val dst = MutableList<Byte>(16) { 0 }
var size = 0

// MIDI1 Channel Voice Messages

ump = Ump(UmpFactory.midi1NoteOff(0, 1, 40, 0x70))
size = UmpTranslator.translateSingleUmpToMidi1Bytes(dst, ump)
var ump = Ump(UmpFactory.midi1NoteOff(0, 1, 40, 0x70))
var size = UmpTranslator.translateSingleUmpToMidi1Bytes(dst, ump)
assertEquals(3, size)
assertContentEquals(listOf(0x81, 40, 0x70).map { it.toByte() }, dst.take(size))

Expand Down Expand Up @@ -92,7 +90,20 @@ class UmpTranslatorTest {
fun testConvertUmpToMidi1Bytes1() {
val umps = listOf(
Ump(0x40904000E0000000),
Ump(0x00201000),
Ump(0x00401000),
Ump(0x4080400000000000),
)
val dst = MutableList<Byte>(9) { 0 }
assertEquals(UmpTranslationResult.OK, UmpTranslator.translateUmpToMidi1Bytes(dst, umps.asSequence()))
val expected = listOf(0, 0x90, 0x40, 0x70, 0x80, 0x20, 0x80, 0x40, 0).map { it.toByte() }
assertContentEquals(expected, dst)
}

@Test
fun testConvertUmpToMidi1Bytes2() {
val umps = listOf(
Ump(0x40904000E0000000),
Ump(0x00201000), // JR Timestamps instead of Delta Clockstamp - still works (we don't have to support it though)
Ump(0x4080400000000000),
)
val dst = MutableList<Byte>(9) { 0 }
Expand Down

0 comments on commit 49ccb32

Please sign in to comment.