All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.korlibs.audio.format.mod.BaseModuleTracker.kt Maven / Gradle / Ivy

The newest version!
package korlibs.audio.format.mod

import korlibs.audio.format.*
import korlibs.audio.sound.*
import korlibs.io.file.*
import korlibs.io.stream.*
import korlibs.logger.*
import korlibs.memory.*
import korlibs.time.*
import kotlin.math.*
import kotlin.time.*

abstract class BaseModuleTracker {
    companion object {
        val LOGGER = Logger("BaseModuleTracker")
    }

    abstract class Format(vararg exts: String) : AudioFormat(*exts) {
        abstract fun createTracker(): BaseModuleTracker
        open suspend fun fastValidate(data: AsyncStream): Boolean = true

        override suspend fun tryReadInfo(data: AsyncStream, props: AudioDecodingProps): Info? {
            try {
                if (!fastValidate(data)) return null
                val time: Duration? = when (props.exactTimings) {
                    true -> {
                        val mod = createTracker()
                        if (!mod.parse(data.readAll().toNBufferUInt8())) return null
                        mod.totalLengthInSamples?.let { samples -> (samples.toDouble() / mod.samplerate.toDouble()).seconds }
                    }
                    else -> null
                }
                return Info(duration = time, channels = 2)
            } catch (e: Throwable) {
                e.printStackTrace()
                return null
            }
        }

        override suspend fun decodeStreamInternal(data: AsyncStream, props: AudioDecodingProps): AudioStream? {
            val mod = createTracker()
            if (!mod.parse(data.readAll().toNBufferUInt8())) return null
            return mod.createAudioStream()
        }
    }

    var samplerate = 44100
    var playing = false
    var endofsong = false

    abstract fun initialize()
    abstract fun parse(buffer: Uint8Buffer): Boolean
    open fun skip(samples: Int) {
        mix(null, samples)
    }
    abstract fun mix(bufs: Array?, buflen: Int = bufs?.get(0)?.size ?: 1024)

    fun parseAndInit(buffer: Uint8Buffer) {
        parse(buffer)
        initialize()
        playing = true
    }

    var totalLengthInSamples: Long? = null

    suspend fun createSoundFromFile(file: VfsFile, soundProvider: NativeSoundProvider = nativeSoundProvider): Sound {
        parseAndInit(file.readBytes().toNBufferUInt8())
        return createSound(soundProvider)
    }

    suspend fun createSound(soundProvider: NativeSoundProvider = nativeSoundProvider): Sound {
        return soundProvider.createStreamingSound(createAudioStream())
    }

    fun createAudioStream(): AudioStream {
        playing = true
        var fch = Array(2) { FloatArray(1024) }
        return object : AudioStream(samplerate, 2) {
            override val finished: Boolean get() = endofsong

            // @TODO: we should figure out how to compute the length in samples/time
            override val totalLengthInSamples: Long?
                get() = [email protected]

            var _currentPositionInSamples: Long = 0L

            private fun skipUntil(newPosition: Long) {
                val startPosition = _currentPositionInSamples
                val seekingTime = measureTime {
                    while (_currentPositionInSamples < newPosition) {
                        val available = newPosition - _currentPositionInSamples
                        val skip = min(available.toInt(), fch[0].size)
                        skip(skip)
                        _currentPositionInSamples += skip
                    }
                }
                LOGGER.warn { "SEEKING from startPosition=$startPosition to newPosition=$newPosition (totalSamples=${newPosition - startPosition}) in $seekingTime" }
            }

            override val currentPositionInSamples: Long get() = _currentPositionInSamples

            override suspend fun seek(position: Duration) {
                val value = estimateSamplesFromTime(position)
                if (_currentPositionInSamples == value) return
                if (value > _currentPositionInSamples) {
                    skipUntil(value)
                } else {
                    //if (value != 0L) error("only supported rewind in MOD value=$value")
                    _currentPositionInSamples = 0L
                    initialize()
                    if (value != 0L) {
                        skipUntil(value)
                    }
                }
            }

            override suspend fun read(out: AudioSamples, offset: Int, length: Int): Int {
                if (fch[0].size < length) fch = Array(2) { FloatArray(length) }
                mix(fch, length)
                _currentPositionInSamples += length
                val l = fch[0]
                val r = fch[1]
                for (n in 0 until length) out.setStereo(offset + n, AudioSample(l[n]), AudioSample(r[n]))
                return length
            }

            override suspend fun clone(): AudioStream {
                return createAudioStream()
            }

        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy