Skip to content

Commit 5a370d4

Browse files
committed
Add wav header file in kotlin
1 parent 156ebdc commit 5a370d4

File tree

3 files changed

+110
-0
lines changed

3 files changed

+110
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package pl.lemanski.pandamidi.io.wav
2+
3+
data class WavFileHeader(
4+
val chunkID: String = "RIFF",
5+
val chunkSize: UInt,
6+
val format: String = "WAVE",
7+
val subchunk1ID: String = "fmt ",
8+
val subchunk1Size: UInt = 16u,
9+
val audioFormat: UShort = 3u, // IEEE float
10+
val numChannels: UShort,
11+
val sampleRate: UInt,
12+
val byteRate: UInt,
13+
val blockAlign: UShort,
14+
val bitsPerSample: UShort = 32u,
15+
val subchunk2ID: String = "data",
16+
val subchunk2Size: UInt
17+
) {
18+
companion object {
19+
fun write(sampleRate: UInt, numSamples: UInt, numChannels: UShort): WavFileHeader {
20+
return WavFileHeader(
21+
chunkSize = 36u + numSamples * numChannels * Float.SIZE_BYTES.toUInt(),
22+
numChannels = numChannels,
23+
sampleRate = sampleRate,
24+
byteRate = sampleRate * numChannels * Float.SIZE_BYTES.toUInt(),
25+
blockAlign = (numChannels * Float.SIZE_BYTES.toUInt()).toUShort(),
26+
subchunk2Size = numSamples * numChannels * Float.SIZE_BYTES.toUInt()
27+
)
28+
}
29+
}
30+
}
31+
32+
expect fun WavFileHeader.toByteArray(): ByteArray
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pl.lemanski.pandamidi.io.wav
2+
3+
import kotlin.test.DefaultAsserter.assertEquals
4+
import kotlin.test.Test
5+
6+
class WavFileHeaderTest {
7+
@OptIn(ExperimentalStdlibApi::class)
8+
@Test
9+
fun should_write_correct_bytes_for_empty_file_header() {
10+
val wavFileHex = WavFileHeader.write(44100u, 0u, 2u).toByteArray().toHexString().uppercase()
11+
val expectedHex = "524946462400000057415645666d7420100000000300020044ac000020620500080020006461746100000000".uppercase()
12+
assertEquals("", expectedHex, wavFileHex)
13+
}
14+
15+
@OptIn(ExperimentalStdlibApi::class)
16+
@Test
17+
fun should_write_correct_bytes_for_non_empty_file_header() {
18+
val wavFileHex = WavFileHeader.write(44100u, 1024u, 2u).toByteArray().toHexString().uppercase()
19+
val expectedHex = "524946462420000057415645666D7420100000000300020044AC000020620500080020006461746100200000".uppercase()
20+
assertEquals("", expectedHex, wavFileHex)
21+
}
22+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package pl.lemanski.pandamidi.io.wav
2+
3+
import kotlinx.cinterop.ByteVar
4+
import kotlinx.cinterop.ByteVarOf
5+
import kotlinx.cinterop.CPointer
6+
import kotlinx.cinterop.CVariable
7+
import kotlinx.cinterop.ExperimentalForeignApi
8+
import kotlinx.cinterop.UIntVar
9+
import kotlinx.cinterop.UIntVarOf
10+
import kotlinx.cinterop.UShortVar
11+
import kotlinx.cinterop.addressOf
12+
import kotlinx.cinterop.cstr
13+
import kotlinx.cinterop.memScoped
14+
import kotlinx.cinterop.pin
15+
import kotlinx.cinterop.plus
16+
import kotlinx.cinterop.pointed
17+
import kotlinx.cinterop.reinterpret
18+
import kotlinx.cinterop.value
19+
import platform.posix.memcpy
20+
21+
@OptIn(ExperimentalForeignApi::class)
22+
actual fun WavFileHeader.toByteArray(): ByteArray {
23+
val byteArray = ByteArray(44)
24+
25+
memScoped {
26+
val buffer = byteArray.pin().addressOf(0).reinterpret<ByteVar>()
27+
28+
memcpy(buffer, chunkID.cstr.ptr, 4u)
29+
buffer.setUIntValueAt(4, chunkSize)
30+
memcpy(buffer.plus(8), format.cstr.ptr, 4u)
31+
memcpy(buffer.plus(12), subchunk1ID.cstr.ptr, 4u)
32+
buffer.setUIntValueAt(16, subchunk1Size)
33+
buffer.setUShortValueAt(20, audioFormat)
34+
buffer.setUShortValueAt(22, numChannels)
35+
buffer.setUIntValueAt(24, sampleRate)
36+
buffer.setUIntValueAt(28, byteRate)
37+
buffer.setUShortValueAt(32, blockAlign)
38+
buffer.setUShortValueAt(34, bitsPerSample)
39+
memcpy(buffer.plus(36), subchunk2ID.cstr.ptr, 4u)
40+
buffer.setUIntValueAt(40, subchunk2Size)
41+
}
42+
43+
return byteArray
44+
}
45+
46+
@OptIn(ExperimentalForeignApi::class)
47+
private fun <T : ByteVarOf<*>> CPointer<T>.setUIntValueAt(index: Int, value: UInt): Int {
48+
this.plus(index)?.reinterpret<UIntVar>()?.pointed?.let { it.value = value } ?: return -1
49+
return 0
50+
}
51+
52+
@OptIn(ExperimentalForeignApi::class)
53+
private fun <T : ByteVarOf<*>> CPointer<T>.setUShortValueAt(index: Int, value: UShort): Int {
54+
this.plus(index)?.reinterpret<UShortVar>()?.pointed?.let { it.value = value } ?: return -1
55+
return 0
56+
}

0 commit comments

Comments
 (0)