Skip to content
This repository has been archived by the owner on Jul 8, 2022. It is now read-only.

Commit

Permalink
Some general + Win32 optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz committed Mar 4, 2021
1 parent f29380f commit fdbd97d
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 94 deletions.
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ group=com.soywiz.korlibs.korau
version=2.0.0-SNAPSHOT

korioVersion=2.0.9
kmemVersion=2.0.9

# bintray location
project.bintray.org=korlibs
Expand Down
1 change: 1 addition & 0 deletions korau/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ kotlin {

dependencies {
commonMainApi("com.soywiz.korlibs.korio:korio:$korioVersion")
commonMainApi("com.soywiz.korlibs.kmem:kmem:$kmemVersion")

def jnaVersion = "5.7.0"
add("jvmMainApi", "net.java.dev.jna:jna:$jnaVersion")
Expand Down
86 changes: 67 additions & 19 deletions korau/src/commonMain/kotlin/com/soywiz/korau/sound/AudioSamples.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.soywiz.korau.sound
import com.soywiz.kds.iterators.*
import com.soywiz.kmem.*
import com.soywiz.korau.internal.*
import com.soywiz.korio.lang.*
import kotlin.math.*

interface IAudioSamples {
Expand All @@ -19,16 +20,23 @@ interface IAudioSamples {
internal fun AudioSamples.resample(scale: Double, totalSamples: Int = (this.totalSamples * scale).toInt(), out: AudioSamples = AudioSamples(channels, totalSamples)): AudioSamples {
val iscale = 1.0 / scale
for (c in 0 until channels) {
val outc = out[c]
val inpc = this[c]
for (n in 0 until totalSamples) {
// @TODO: Increase quality
outc[n] = inpc[(n * iscale).toInt()]
fastShortTransfer.use(out[c]) { outc ->
for (n in 0 until totalSamples) {
// @TODO: Increase quality
outc[n] = inpc[(n * iscale).toInt()]
}
}
}
return out
}

fun AudioSamples.resample(srcFreq: Int, dstFreq: Int): AudioSamples =
resample(dstFreq.toDouble() / srcFreq.toDouble())

fun AudioSamples.resampleIfRequired(srcFreq: Int, dstFreq: Int): AudioSamples =
if (srcFreq == dstFreq) this else resample(srcFreq, dstFreq)

class AudioSamplesProcessor(val channels: Int, val totalSamples: Int, val data: Array<FloatArray> = Array(channels) { FloatArray(totalSamples) }) {
fun reset(): AudioSamplesProcessor {
for (ch in 0 until channels) data[ch].fill(0f)
Expand Down Expand Up @@ -73,6 +81,7 @@ class AudioSamplesProcessor(val channels: Int, val totalSamples: Int, val data:

class AudioSamples(override val channels: Int, override val totalSamples: Int, val data: Array<ShortArray> = Array(channels) { ShortArray(totalSamples) }) : IAudioSamples {
//val interleaved by lazy { interleaved() }
internal val fastShortTransfer = FastShortTransfer()

operator fun get(channel: Int): ShortArray = data[channel]

Expand Down Expand Up @@ -109,7 +118,7 @@ class AudioSamples(override val channels: Int, override val totalSamples: Int, v

class AudioSamplesInterleaved(override val channels: Int, override val totalSamples: Int, val data: ShortArray = ShortArray(totalSamples * channels)) : IAudioSamples {
//val separared by lazy { separated() }

internal val fastShortTransfer = FastShortTransfer()

private fun index(channel: Int, sample: Int) = (sample * channels) + channel
override operator fun get(channel: Int, sample: Int): Short = data[index(channel, sample)]
Expand All @@ -126,13 +135,49 @@ fun AudioSamples.copyOfRange(start: Int, end: Int): AudioSamples {
return out
}

fun AudioSamples.interleaved(out: AudioSamplesInterleaved = AudioSamplesInterleaved(channels, totalSamples)): AudioSamplesInterleaved {
assert(out.data.size >= totalSamples * channels)
when (channels) {
1 -> arraycopy(this.data[0], 0, out.data, 0, totalSamples)
2 -> arrayinterleave(
out.data, 0,
this.data[0], 0,
this.data[1], 0,
totalSamples,
out.fastShortTransfer
)
else -> {
out.fastShortTransfer.use(out.data) { outData ->
val channels = channels
for (c in 0 until channels) {
var m = c
for (n in 0 until totalSamples) {
outData[m] = this[c, n]
m += channels
}
}
}
}
}
return out
}

fun IAudioSamples.interleaved(out: AudioSamplesInterleaved = AudioSamplesInterleaved(channels, totalSamples)): AudioSamplesInterleaved {
val channels = channels
for (c in 0 until channels) {
var m = c
for (n in 0 until totalSamples) {
out.data[m] = this[c, n]
m += channels
assert(out.data.size >= totalSamples * channels)
when (this) {
is AudioSamples -> this.interleaved(out)
is AudioSamplesInterleaved -> arraycopy(this.data, 0, out.data, 0, totalSamples * channels)
else -> {
out.fastShortTransfer.use(out.data) { outData ->
val channels = channels
for (c in 0 until channels) {
var m = c
for (n in 0 until totalSamples) {
outData[m] = this[c, n]
m += channels
}
}
}
}
}
return out
Expand All @@ -147,14 +192,17 @@ fun AudioSamplesInterleaved.applyProps(speed: Double, panning: Double, volume: D
val rratio = ((((panning + 1.0) / 2.0).clamp01()) * volume).toFloat()
val lratio = ((1.0 - rratio) * volume).toFloat()

if (channels == 2) {
for (n in 0 until out.totalSamples) {
out[0, n] = (this[0, (n * speedf).toInt()] * lratio).toInt().toShort()
out[1, n] = (this[1, (n * speedf).toInt()] * rratio).toInt().toShort()
}
} else {
for (n in out.data.indices) {
out.data[n] = (this.data[(n * speedf).toInt()] * lratio).toInt().toShort()
out.fastShortTransfer.use(out.data) { outData ->
var m = 0
if (channels == 2) {
for (n in 0 until out.totalSamples) {
outData[m++] = (outData[(n * speedf).toInt() * 2 + 0] * lratio).toInt().toShort()
outData[m++] = (outData[(n * speedf).toInt() * 2 + 1] * rratio).toInt().toShort()
}
} else {
for (n in out.data.indices) {
outData[m++] = (outData[(n * speedf).toInt()] * lratio).toInt().toShort()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.soywiz.korau.sound

import kotlin.test.*

class AudioSamplesTest {
@Test
fun test() {
val samples = AudioSamples(2, 22050)
val samples2 = samples.resample(22050, 44100)
assertEquals(44100, samples2.totalSamples)
}
}
150 changes: 102 additions & 48 deletions korau/src/mingwX64Main/kotlin/com/soywiz/korau/sound/WaveOutProcess.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,40 @@ private class ConcurrentDeque<T : Any> {
private interface WaveOutPart

private object WaveOutEnd : WaveOutPart
private object WaveOutFlush : WaveOutPart

private class WaveOutData(val data: ShortArray) : WaveOutPart {
private class WaveOutReopen(val freq: Int) : WaveOutPart {
init {
this.freeze()
}
}
private class WaveOutSetVolume(val volume: Double) : WaveOutPart {

private interface WaveOutDataBase : WaveOutPart {
fun computeData(): ShortArray
}

//private class WaveOutData(val data: ShortArray) : WaveOutDataBase {
// override fun computeData(): ShortArray = data
//
// init {
// this.freeze()
// }
//}

private class WaveOutDataEx(
val adata: Array<ShortArray>,
val pitch: Double,
val volume: Double,
val panning: Double,
val freq: Int
) : WaveOutDataBase {
override fun computeData(): ShortArray =
AudioSamples(2, adata[0].size, Array(2) { adata[it % adata.size] })
//.resampleIfRequired(freq, 44100)
.interleaved()
.applyProps(pitch, panning, volume)
.data

init {
this.freeze()
}
Expand All @@ -53,32 +80,45 @@ class WaveOutProcess(val freq: Int, val nchannels: Int) {
private val sPosition = AtomicLong(0L)
private val sLength = AtomicLong(0L)
private val completed = AtomicLong(0L)
private val numPendingChunks = AtomicLong(0L)
private val deque = ConcurrentDeque<WaveOutPart>()
private val info = AtomicReference<Future<Unit>?>(null)

val position get() = sPosition.value
val length get() = sLength.value
val isCompleted get() = completed.value != 0L
//val isCompleted get() = completed.value != 0L
val pendingAudio get() = numPendingChunks.value != 0L || deque.size > 0

init {
freeze()
}

val pendingCommands get() = deque.size

fun addData(data: ShortArray) {
sLength.addAndGet(data.size / nchannels)
deque.add(WaveOutData(data))
}

fun setVolume(volume: Double) {
deque.add(WaveOutSetVolume(volume))
//fun addData(data: ShortArray) {
// sLength.addAndGet(data.size / nchannels)
// deque.add(WaveOutData(data))
//}

fun addData(samples: AudioSamples, offset: Int, size: Int, pitch: Double, volume: Double, panning: Double, freq: Int) {
sLength.addAndGet(size)
deque.add(WaveOutDataEx(
Array(samples.channels) { samples.data[it].copyOfRange(offset, offset + size) },
pitch, volume, panning, freq
))
}

fun stop() {
deque.add(WaveOutEnd)
}

fun reopen(freq: Int) {
sPosition.value = 0L
sLength.value = 0L
completed.value = 0L
deque.add(WaveOutReopen(freq))
}

fun stopAndWait() {
stop()
info?.value?.consume { }
Expand All @@ -88,73 +128,87 @@ class WaveOutProcess(val freq: Int, val nchannels: Int) {
val _info = this
_info.info.value = _worker.execute(TransferMode.SAFE, { _info }) { info ->
memScoped {
val format = alloc<WAVEFORMATEX>().apply {
this.wFormatTag = WAVE_FORMAT_PCM.convert()
this.nChannels = info.nchannels.convert() // 2?
this.nSamplesPerSec = info.freq.convert() // 44100?
this.wBitsPerSample = Short.SIZE_BITS.convert() // 16
this.nBlockAlign = (info.nchannels * Short.SIZE_BYTES).convert()
this.nAvgBytesPerSec = this.nSamplesPerSec * this.nBlockAlign
this.cbSize = sizeOf<WAVEFORMATEX>().convert()
//this.cbSize = 0.convert()
}
val nchannels = info.nchannels // 2
val hWaveOut = alloc<HWAVEOUTVar>()

waveOutOpen(hWaveOut.ptr, WAVE_MAPPER, format.ptr, 0.convert(), 0.convert(), CALLBACK_NULL)
val pendingChunks = ArrayDeque<WaveOutChunk>()

fun updatePosition() {
// Update position
memScoped {
val time = alloc<MMTIME>()
time.wType = TIME_BYTES.convert()
waveOutGetPosition(hWaveOut!!.value, time.ptr, sizeOf<MMTIME>().convert())
//info.position.value = time.u.cb.toLong() / Short.SIZE_BYTES / info.nchannels
info.sPosition.value = time.u.cb.toLong() / info.nchannels
}
}

fun clearCompletedChunks() {
updatePosition()
while (pendingChunks.isNotEmpty() && pendingChunks.first().completed) {
val chunk = pendingChunks.removeFirst()
waveOutUnprepareHeader(hWaveOut.value, chunk.hdr.ptr, sizeOf<WAVEHDR>().convert())
info.sPosition.addAndGet(chunk.data.size / nchannels)
chunk.dispose()
}
}

fun waveReset() {
clearCompletedChunks()
while (pendingChunks.isNotEmpty()) {
Sleep(5.convert())
clearCompletedChunks()
}
waveOutReset(hWaveOut.value)
info.sPosition.value = 0L
}

fun waveClose() {
waveReset()
waveOutClose(hWaveOut.value)
}

var openedFreq = 0

fun waveOpen(freq: Int) {
openedFreq = freq
memScoped {
val format = alloc<WAVEFORMATEX>().apply {
this.wFormatTag = WAVE_FORMAT_PCM.convert()
this.nChannels = nchannels.convert() // 2?
this.nSamplesPerSec = freq.convert()
this.wBitsPerSample = Short.SIZE_BITS.convert() // 16
this.nBlockAlign = (info.nchannels * Short.SIZE_BYTES).convert()
this.nAvgBytesPerSec = this.nSamplesPerSec * this.nBlockAlign
this.cbSize = sizeOf<WAVEFORMATEX>().convert()
//this.cbSize = 0.convert()
}

waveOutOpen(hWaveOut.ptr, WAVE_MAPPER, format.ptr, 0.convert(), 0.convert(), CALLBACK_NULL)
}
}

waveOpen(info.freq)

try {
process@while (true) {
Sleep(5.convert())
updatePosition()
clearCompletedChunks()
while (true) {
val it = info.deque.consume() ?: break
//println("CONSUME: $item")
when (it) {
is WaveOutReopen -> {
if (it.freq != openedFreq) {
waveClose()
waveOpen(it.freq)
}
}
is WaveOutEnd -> break@process
is WaveOutData -> {
val chunk = WaveOutChunk(it.data)
is WaveOutDataBase -> {
val chunk = WaveOutChunk(it.computeData())
//info.sLength.addAndGet(chunk.data.size / info.nchannels)
pendingChunks.add(chunk)
waveOutPrepareHeader(hWaveOut.value, chunk.hdr.ptr, sizeOf<WAVEHDR>().convert())
waveOutWrite(hWaveOut.value, chunk.hdr.ptr, sizeOf<WAVEHDR>().convert())
clearCompletedChunks()
}
is WaveOutSetVolume -> {
waveOutSetVolume(hWaveOut.value, (it.volume.clamp01() * 0xFFFF).toInt().convert())
is WaveOutFlush -> {
waveReset()
}
}

}
Sleep(5.convert())
}
} finally {
//println("finalizing...")
while (pendingChunks.isNotEmpty()) {
Sleep(5.convert())
clearCompletedChunks()
}
waveOutReset(hWaveOut.value)
waveOutClose(hWaveOut.value)
waveClose()
info.completed.value = 1L
}
}
Expand Down
Loading

0 comments on commit fdbd97d

Please sign in to comment.