Skip to content

Commit 031b928

Browse files
committed
working version with audio output
1 parent 0b9e837 commit 031b928

File tree

11 files changed

+227
-173
lines changed

11 files changed

+227
-173
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ plugins {
33
}
44

55
kotlin {
6-
linuxX64().apply {
6+
mingwX64().apply {
77
binaries.executable {
88
entryPoint = "main"
99
}

app/src/nativeMain/kotlin/Main.kt

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import kotlinx.cinterop.ExperimentalForeignApi
22
import kotlinx.cinterop.FloatVar
33
import kotlinx.cinterop.sizeOf
4+
import kotlinx.coroutines.coroutineScope
5+
import kotlinx.coroutines.launch
6+
import kotlinx.coroutines.runBlocking
47
import kotlinx.io.Buffer
58
import kotlinx.io.files.Path
69
import kotlinx.io.files.SystemFileSystem
710
import pl.lemanski.pandamidi.generator.MidiMessageNoteOff
811
import pl.lemanski.pandamidi.generator.MidiMessageNoteOn
912
import pl.lemanski.pandamidi.generator.getGenerator
13+
import pl.lemanski.pandamidi.io.audio.playFromBuffer
1014
import pl.lemanski.pandamidi.io.toByteArrayLittleEndian
1115
import pl.lemanski.pandamidi.io.wav.WavFileHeader
12-
import pl.lemanski.pandamidi.io.wav.toByteArray
1316
import platform.posix.sleep
1417

1518
@OptIn(ExperimentalForeignApi::class)
@@ -62,23 +65,35 @@ fun main(args: Array<String>) {
6265
var bytes = ByteArray(0)
6366
val generator = getGenerator()
6467

65-
val soundFontPath = Path(args[0])
68+
// val soundFontPath = Path(args[0])
69+
val soundFontPath = Path("C:\\Users\\Mikolaj\\Desktop\\test\\florestan-subset.sf2")
6670

6771
generator.setSoundFont(soundFontPath.toString())
6872
val midiBytes = generator.generate(c)
6973
val numSamples = midiBytes.size.toUInt() / sizeOf<FloatVar>().toUInt()
7074
val wavFileHeader = WavFileHeader.write(44100u, numSamples, 2u)
7175

72-
bytes += wavFileHeader.toByteArray()
76+
// bytes += wavFileHeader.toByteArray()
7377
bytes += midiBytes.toByteArrayLittleEndian()
7478

75-
println(bytes.size)
76-
77-
val file = Buffer()
7879

79-
file.write(bytes, 0, bytes.size)
80-
81-
SystemFileSystem.sink(Path("./output.wav")).write(file, file.size)
80+
println(bytes.size)
8281

83-
sleep(5u)
82+
runBlocking {
83+
coroutineScope {
84+
launch {
85+
println("Start play")
86+
playFromBuffer(bytes)
87+
sleep(10u)
88+
println("End play")
89+
}
90+
}
91+
}
92+
// val file = Buffer()
93+
//
94+
// file.write(bytes, 0, bytes.size)
95+
//
96+
// SystemFileSystem.sink(Path("./output.wav")).write(file, file.size)
97+
//
98+
// sleep(5u)
8499
}

core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ kotlin {
1818
commonMain.dependencies {
1919
implementation(libs.coroutines.core)
2020
implementation(projects.tinySoundFont)
21+
implementation(libs.mikroaudio)
2122
}
2223

2324
commonTest.dependencies {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pl.lemanski.pandamidi.io.audio
2+
3+
import pl.lemanski.mikroaudio.MikroAudio
4+
5+
fun playFromBuffer(byteArray: ByteArray) {
6+
MikroAudio.playback(byteArray)
7+
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ kotlin = "2.0.0"
33
coroutines = "1.9.0-RC"
44
pandaMidi = "0.0.1"
55
kotlinxIo = "0.5.1"
6+
mikroaudio = "0.0.1"
67

78
[libraries]
89
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
910
kotlinx-io = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinxIo" }
1011
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
12+
mikroaudio = { module = "pl.lemanski.mikroaudio:core", version.ref = "mikroaudio" }
1113

1214
[plugins]
1315
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package pl.lemanski.tinySoundFont.internal
22

33
import pl.lemanski.tinySoundFont.Channel
4+
import pl.lemanski.tinySoundFont.SoundFont
45

5-
internal expect class ChannelDelegate : Channel
6+
internal expect fun getChannelDelegate(
7+
number: Int,
8+
soundFont: SoundFont
9+
): Channel

tinySoundFont/src/commonMain/kotlin/pl/lemanski/tinySoundFont/internal/SoundFontDelegate.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ package pl.lemanski.tinySoundFont.internal
22

33
import pl.lemanski.tinySoundFont.SoundFont
44

5-
internal expect class SoundFontDelegate : SoundFont
5+
internal expect fun getSoundFontDelegate(path: String): SoundFont
6+
7+
internal expect fun getSoundFontDelegate(memory: ByteArray): SoundFont
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package pl.lemanski.tinySoundFont
22

3-
import pl.lemanski.tinySoundFont.internal.SoundFontDelegate
3+
import pl.lemanski.tinySoundFont.internal.getSoundFontDelegate
44

55
actual fun soundFont(path: String): SoundFont {
6-
return SoundFontDelegate(path)
6+
return getSoundFontDelegate(path)
77

88
}
99

1010
actual fun soundFont(memory: ByteArray): SoundFont {
11-
return SoundFontDelegate(memory)
11+
return getSoundFontDelegate(memory)
1212
}
Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import kotlinx.cinterop.CPointer
44
import kotlinx.cinterop.ExperimentalForeignApi
55
import kotlinx.cinterop.reinterpret
66
import pl.lemanski.tinySoundFont.Channel
7+
import pl.lemanski.tinySoundFont.SoundFont
78
import tinySoundFont.tsf_channel_get_pan
89
import tinySoundFont.tsf_channel_get_pitchrange
910
import tinySoundFont.tsf_channel_get_pitchwheel
@@ -26,10 +27,15 @@ import tinySoundFont.tsf_channel_set_tuning
2627
import tinySoundFont.tsf_channel_set_volume
2728
import tinySoundFont.tsf_channel_sounds_off_all
2829

30+
internal actual fun getChannelDelegate(
31+
number: Int,
32+
soundFont: SoundFont
33+
): Channel = ChannelDelegate(number, soundFont)
34+
2935
@OptIn(ExperimentalForeignApi::class)
30-
internal actual class ChannelDelegate(
36+
internal class ChannelDelegate(
3137
override val number: Int,
32-
private val soundFontDelegate: SoundFontDelegate
38+
private val soundFont: SoundFont
3339
) : Channel {
3440

3541
override fun setPresetIndex(presetIndex: Int) {
@@ -40,7 +46,12 @@ internal actual class ChannelDelegate(
4046

4147
override fun setPresetNumber(presetNumber: Int, isMidiDrums: Boolean) {
4248
withSoundFont {
43-
tsf_channel_set_presetnumber(it.reinterpret(), number, presetNumber, if (isMidiDrums) 1 else 0)
49+
tsf_channel_set_presetnumber(
50+
it.reinterpret(),
51+
number,
52+
presetNumber,
53+
if (isMidiDrums) 1 else 0
54+
)
4455
}
4556
}
4657

@@ -158,5 +169,8 @@ internal actual class ChannelDelegate(
158169
}
159170
}
160171

161-
private fun <T> withSoundFont(block: (soundFont: CPointer<*>) -> T): T = soundFontDelegate.soundFont.let(block) ?: throw IllegalStateException("SoundFont not loaded")
172+
// FIXME
173+
private fun <T> withSoundFont(block: (soundFont: CPointer<*>) -> T): T =
174+
(soundFont as? SoundFontDelegate)?.soundFont?.let(block)
175+
?: throw IllegalStateException("SoundFont not loaded")
162176
}
Lines changed: 0 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +0,0 @@
1-
package pl.lemanski.tinySoundFont.internal
2-
3-
import kotlinx.cinterop.CPointer
4-
import kotlinx.cinterop.ExperimentalForeignApi
5-
import kotlinx.cinterop.refTo
6-
import kotlinx.cinterop.reinterpret
7-
import kotlinx.cinterop.toCValues
8-
import kotlinx.cinterop.toKString
9-
import pl.lemanski.tinySoundFont.Channel
10-
import pl.lemanski.tinySoundFont.SoundFont
11-
import tinySoundFont.TSFOutputMode
12-
import tinySoundFont.tsf_active_voice_count
13-
import tinySoundFont.tsf_bank_get_presetname
14-
import tinySoundFont.tsf_bank_note_off
15-
import tinySoundFont.tsf_bank_note_on
16-
import tinySoundFont.tsf_channel_set_bank_preset
17-
import tinySoundFont.tsf_get_presetcount
18-
import tinySoundFont.tsf_get_presetindex
19-
import tinySoundFont.tsf_get_presetname
20-
import tinySoundFont.tsf_load_filename
21-
import tinySoundFont.tsf_load_memory
22-
import tinySoundFont.tsf_note_off
23-
import tinySoundFont.tsf_note_off_all
24-
import tinySoundFont.tsf_note_on
25-
import tinySoundFont.tsf_render_float
26-
import tinySoundFont.tsf_reset
27-
import tinySoundFont.tsf_set_max_voices
28-
import tinySoundFont.tsf_set_output
29-
import tinySoundFont.tsf_set_volume
30-
31-
@OptIn(ExperimentalForeignApi::class)
32-
internal actual class SoundFontDelegate : SoundFont {
33-
internal val soundFont: CPointer<*>
34-
override val channels: List<Channel>
35-
36-
constructor(path: String) {
37-
this.soundFont = tsf_load_filename(path) ?: throw IllegalStateException("SoundFont not loaded")
38-
this.channels = (0..getPresetsCount()).map { ChannelDelegate(it, this) }
39-
}
40-
41-
constructor(memory: ByteArray) {
42-
this.soundFont = tsf_load_memory(memory.toCValues(), memory.size) ?: throw IllegalStateException("SoundFont not loaded")
43-
this.channels = (0..getPresetsCount()).map { ChannelDelegate(it, this) }
44-
}
45-
46-
override fun reset() {
47-
withSoundFont {
48-
tsf_reset(it.reinterpret())
49-
}
50-
}
51-
52-
override fun getPresetIndex(bank: Int, presetNumber: Int): Int {
53-
return withSoundFont {
54-
tsf_get_presetindex(it.reinterpret(), bank, presetNumber)
55-
}
56-
}
57-
58-
override fun getPresetsCount(): Int {
59-
return withSoundFont {
60-
tsf_get_presetcount(it.reinterpret())
61-
}
62-
}
63-
64-
override fun getPresetName(presetIndex: Int): String {
65-
return withSoundFont {
66-
val buffer = tsf_get_presetname(it.reinterpret(), presetIndex) ?: return@withSoundFont ""
67-
buffer.toKString()
68-
}
69-
}
70-
71-
override fun bankGetPresetName(bank: Int, presetNumber: Int): String {
72-
return withSoundFont {
73-
val buffer = tsf_bank_get_presetname(it.reinterpret(), bank, presetNumber) ?: return@withSoundFont ""
74-
buffer.toKString()
75-
}
76-
}
77-
78-
override fun setOutput(outputMode: SoundFont.OutputMode, sampleRate: Int, globalGainDb: Float) {
79-
return withSoundFont {
80-
val tsfMode = when (outputMode) {
81-
SoundFont.OutputMode.TSF_STEREO_INTERLEAVED -> TSFOutputMode.TSF_STEREO_INTERLEAVED
82-
SoundFont.OutputMode.TSF_STEREO_UNWEAVED -> TSFOutputMode.TSF_STEREO_UNWEAVED
83-
SoundFont.OutputMode.TSF_MONO -> TSFOutputMode.TSF_MONO
84-
}
85-
tsf_set_output(it.reinterpret(), tsfMode, sampleRate, globalGainDb)
86-
}
87-
}
88-
89-
override fun setVolume(globalGain: Float) {
90-
withSoundFont {
91-
tsf_set_volume(it.reinterpret(), globalGain)
92-
}
93-
}
94-
95-
override fun setMaxVoices(maxVoices: Int) {
96-
withSoundFont {
97-
tsf_set_max_voices(it.reinterpret(), maxVoices)
98-
}
99-
}
100-
101-
override fun noteOn(presetIndex: Int, key: Int, velocity: Float) {
102-
withSoundFont {
103-
tsf_note_on(it.reinterpret(), presetIndex, key, velocity)
104-
}
105-
}
106-
107-
override fun bankNoteOn(bank: Int, presetNumber: Int, key: Int, velocity: Float) {
108-
withSoundFont {
109-
tsf_bank_note_on(it.reinterpret(), bank, presetNumber, key, velocity)
110-
}
111-
}
112-
113-
override fun noteOff(presetIndex: Int, key: Int) {
114-
withSoundFont {
115-
tsf_note_off(it.reinterpret(), presetIndex, key)
116-
}
117-
}
118-
119-
override fun bankNoteOff(bank: Int, presetNumber: Int, key: Int) {
120-
withSoundFont {
121-
tsf_bank_note_off(it.reinterpret(), bank, presetNumber, key)
122-
}
123-
}
124-
125-
override fun noteOffAll() {
126-
withSoundFont {
127-
tsf_note_off_all(it.reinterpret())
128-
}
129-
}
130-
131-
override fun activeVoiceCount(): Int {
132-
return withSoundFont {
133-
tsf_active_voice_count(it.reinterpret())
134-
}
135-
}
136-
137-
override fun setBankPreset(channel: Int, bank: Int, presetNumber: Int) {
138-
withSoundFont {
139-
tsf_channel_set_bank_preset(it.reinterpret(), channel, bank, presetNumber)
140-
}
141-
}
142-
143-
override fun renderFloat(samples: Int, isMixing: Boolean): FloatArray {
144-
return withSoundFont {
145-
val buffer = FloatArray(samples * 2)
146-
val flagMixing = if (isMixing) 1 else 0
147-
tsf_render_float(it.reinterpret(), buffer.refTo(0), samples, flagMixing)
148-
buffer
149-
}
150-
}
151-
152-
private fun <T> withSoundFont(block: (soundFont: CPointer<*>) -> T): T = this.soundFont.let(block) ?: throw IllegalStateException("SoundFont not loaded")
153-
}

0 commit comments

Comments
 (0)