diff --git a/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Music.kt b/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Music.kt index 8a8d3b6da..196317c07 100644 --- a/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Music.kt +++ b/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Music.kt @@ -125,11 +125,19 @@ internal class Midi2TrackMerger(private var source: Midi2Music) { internal fun getMergedMessages(): Midi2Music { var l = mutableListOf>() + 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)) } @@ -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) @@ -245,9 +258,16 @@ open class Midi2TrackSplitter(private val source: MutableList) { 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) } diff --git a/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Player.kt b/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Player.kt index 546ef4515..92bf3461b 100644 --- a/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Player.kt +++ b/ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/Midi2Player.kt @@ -13,8 +13,13 @@ internal class Midi2EventLooper(var messages: List, 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 } @@ -233,7 +238,12 @@ internal class Midi2SimpleSeekProcessor(ticks: Int) : SeekProcessor { 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) { diff --git a/ktmidi/src/commonTest/kotlin/dev/atsushieno/ktmidi/UmpTranslatorTest.kt b/ktmidi/src/commonTest/kotlin/dev/atsushieno/ktmidi/UmpTranslatorTest.kt index d2ae8ccdd..b0b06c46c 100644 --- a/ktmidi/src/commonTest/kotlin/dev/atsushieno/ktmidi/UmpTranslatorTest.kt +++ b/ktmidi/src/commonTest/kotlin/dev/atsushieno/ktmidi/UmpTranslatorTest.kt @@ -8,14 +8,12 @@ class UmpTranslatorTest { @Test fun testConvertSingleUmpToMidi1 () { - var ump = Ump(0L) val dst = MutableList(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)) @@ -92,7 +90,20 @@ class UmpTranslatorTest { fun testConvertUmpToMidi1Bytes1() { val umps = listOf( Ump(0x40904000E0000000), - Ump(0x00201000), + Ump(0x00401000), + Ump(0x4080400000000000), + ) + val dst = MutableList(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(9) { 0 }