Skip to content

Commit a1ec4a7

Browse files
committed
Added basic bindings for tiny sound font lib
1 parent 49c00a0 commit a1ec4a7

File tree

10 files changed

+180
-182
lines changed

10 files changed

+180
-182
lines changed

app/src/nativeMain/kotlin/Main.kt

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,47 @@ import platform.posix.sleep
1515
@OptIn(ExperimentalForeignApi::class)
1616
fun main() {
1717
val gOff = MidiMessageNoteOff(
18-
time = 10000u,
19-
channel = 5u,
20-
key = 32u,
18+
time = 10000,
19+
channel = 5,
20+
key = 32,
2121
next = null
2222
)
2323

2424
val g = MidiMessageNoteOn(
25-
time = 2500u,
26-
channel = 5u,
27-
key = 43u,
28-
velocity = 80u,
25+
time = 2500,
26+
channel = 5,
27+
key = 43,
28+
velocity = 80,
2929
next = gOff
3030
)
3131

3232
val eOff = MidiMessageNoteOff(
33-
time = 2000u,
34-
channel = 7u,
35-
key = 40u,
33+
time = 2000,
34+
channel = 7,
35+
key = 40,
3636
next = g
3737
)
3838

3939
val e = MidiMessageNoteOn(
40-
time = 1_000u,
41-
channel = 7u,
42-
key = 40u,
43-
velocity = 80u,
40+
time = 1_000,
41+
channel = 7,
42+
key = 40,
43+
velocity = 80,
4444
next = eOff
4545
)
4646

4747
val cOff = MidiMessageNoteOff(
48-
time = 100u,
49-
channel = 1u,
50-
key = 36u,
48+
time = 100,
49+
channel = 1,
50+
key = 36,
5151
next = e
5252
)
5353

5454
val c = MidiMessageNoteOn(
55-
time = 0u,
56-
channel = 1u,
57-
key = 36u,
58-
velocity = 80u,
55+
time = 0,
56+
channel = 1,
57+
key = 36,
58+
velocity = 80,
5959
next = cOff
6060
)
6161

@@ -64,8 +64,11 @@ fun main() {
6464

6565
val soundFontPath = Path("D:\\src\\MidiWavConverter\\Example\\florestan-subset.sf2")
6666

67+
println("1")
6768
generator.setSoundFont(soundFontPath.toString())
69+
println("2")
6870
val midiBytes = generator.generate(c)
71+
println("3")
6972
val numSamples = midiBytes.size.toUInt() / sizeOf<FloatVar>().toUInt()
7073
val wavFileHeader = WavFileHeader.write(44100u, numSamples, 2u)
7174

core/src/commonMain/kotlin/pl/lemanski/pandamidi/generator/MidiMessage.kt

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
package pl.lemanski.pandamidi.generator
22

33
sealed interface MidiMessage {
4-
val time: UInt
5-
val channel: UInt
4+
val time: Int
5+
val channel: Int
66
val type: Type
77
var next: MidiMessage?
88

9-
enum class Type(val value: UInt) {
10-
NOTE_OFF(0x80u),
11-
NOTE_ON(0x90u),
12-
KEY_PRESSURE(0xA0u),
13-
CONTROL_CHANGE(0xB0u),
14-
PROGRAM_CHANGE(0xC0u),
15-
CHANNEL_PRESSURE(0xD0u),
16-
PITCH_BEND(0xE0u),
17-
SET_TEMPO(0x51u)
9+
enum class Type(val value: Int) {
10+
NOTE_OFF(0x80),
11+
NOTE_ON(0x90),
12+
KEY_PRESSURE(0xA0),
13+
CONTROL_CHANGE(0xB0),
14+
PROGRAM_CHANGE(0xC0),
15+
CHANNEL_PRESSURE(0xD0),
16+
PITCH_BEND(0xE0),
17+
SET_TEMPO(0x51)
1818
}
1919
}
2020

2121
// ---
2222

2323
data class MidiMessageNoteOn(
24-
override val time: UInt,
25-
override val channel: UInt,
26-
val key: UInt,
27-
val velocity: UInt,
24+
override val time: Int,
25+
override val channel: Int,
26+
val key: Int,
27+
val velocity: Int,
2828
override var next: MidiMessage?,
2929
) : MidiMessage {
3030
override val type: MidiMessage.Type = MidiMessage.Type.NOTE_ON
@@ -33,9 +33,9 @@ data class MidiMessageNoteOn(
3333
// ---
3434

3535
data class MidiMessageNoteOff(
36-
override val time: UInt,
37-
override val channel: UInt,
38-
val key: UInt,
36+
override val time: Int,
37+
override val channel: Int,
38+
val key: Int,
3939
override var next: MidiMessage?,
4040
) : MidiMessage {
4141
override val type: MidiMessage.Type = MidiMessage.Type.NOTE_OFF
@@ -44,9 +44,9 @@ data class MidiMessageNoteOff(
4444
// ---
4545

4646
data class MidiMessageProgramChange(
47-
override val time: UInt,
48-
override val channel: UInt,
49-
val program: UInt,
47+
override val time: Int,
48+
override val channel: Int,
49+
val program: Int,
5050
override var next: MidiMessage?,
5151
) : MidiMessage {
5252
override val type: MidiMessage.Type = MidiMessage.Type.PROGRAM_CHANGE
@@ -55,9 +55,9 @@ data class MidiMessageProgramChange(
5555
// ---
5656

5757
data class MidiMessagePitchBend(
58-
override val time: UInt,
59-
override val channel: UInt,
60-
val pitchBend: UInt,
58+
override val time: Int,
59+
override val channel: Int,
60+
val pitchBend: Int,
6161
override var next: MidiMessage?,
6262
) : MidiMessage {
6363
override val type: MidiMessage.Type = MidiMessage.Type.PITCH_BEND
@@ -66,10 +66,10 @@ data class MidiMessagePitchBend(
6666
// ---
6767

6868
data class MidiMessageControlChange(
69-
override val time: UInt,
70-
override val channel: UInt,
71-
val control: UInt,
72-
val controlValue: UInt,
69+
override val time: Int,
70+
override val channel: Int,
71+
val control: Int,
72+
val controlValue: Int,
7373
override var next: MidiMessage?,
7474
): MidiMessage {
7575
override val type: MidiMessage.Type = MidiMessage.Type.CONTROL_CHANGE

core/src/commonMain/kotlin/pl/lemanski/pandamidi/soundFont/SoundFont.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ interface SoundFont {
108108
*/
109109
fun activeVoiceCount(): Int
110110

111+
/**
112+
* Set up channel parameters
113+
*/
114+
fun setBankPreset(channel: Int, bank: Int, presetNumber: Int)
115+
116+
/**
117+
* Render output samples
118+
*/
119+
fun renderFloat(samples: Int, isMixing: Boolean): FloatArray
120+
111121
/**
112122
* Supported output modes by the render methods
113123
*/

core/src/commonMain/kotlin/pl/lemanski/pandamidi/soundFont/SoundFontExt.kt

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ package pl.lemanski.pandamidi.soundFont
22

33
import pl.lemanski.pandamidi.soundFont.internal.SoundFontDelegate
44

5-
internal typealias Sf = Any
6-
7-
fun SoundFont.load(path: String): SoundFont {
8-
val soundFont = loadFromFile(path)
9-
return object : SoundFont by SoundFontDelegate(soundFont) { }
5+
fun soundFont(path: String): SoundFont {
6+
loadFromFile(path)
7+
return object : SoundFont by SoundFontDelegate() { }
108
}
119

12-
fun SoundFont.load(memory: ByteArray): SoundFont {
13-
val soundFont = loadFromMemory(memory)
14-
return object : SoundFont by SoundFontDelegate(soundFont) { }
10+
fun soundFont(memory: ByteArray): SoundFont {
11+
loadFromMemory(memory)
12+
return object : SoundFont by SoundFontDelegate() { }
1513
}
1614

17-
internal expect fun loadFromFile(path: String): Sf
15+
internal expect fun loadFromFile(path: String)
1816

19-
internal expect fun loadFromMemory(memory: ByteArray): Sf
17+
internal expect fun loadFromMemory(memory: ByteArray)

core/src/commonMain/kotlin/pl/lemanski/pandamidi/soundFont/internal/SoundFontDelegate.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package pl.lemanski.pandamidi.soundFont.internal
22

3-
import pl.lemanski.pandamidi.soundFont.Sf
43
import pl.lemanski.pandamidi.soundFont.SoundFont
54

6-
internal open class SoundFontDelegate(val sf: Sf) : SoundFont {
5+
internal open class SoundFontDelegate : SoundFont {
76

8-
override fun copy(): SoundFont = SoundFontDelegate(sf)
7+
override fun copy(): SoundFont = SoundFontDelegate() // TODO implement
98

109
override fun close() = close(this)
1110

@@ -36,4 +35,8 @@ internal open class SoundFontDelegate(val sf: Sf) : SoundFont {
3635
override fun noteOffAll() = noteOffAll(this)
3736

3837
override fun activeVoiceCount(): Int = activeVoiceCount(this)
38+
39+
override fun setBankPreset(channel: Int, bank: Int, presetNumber: Int) = setBankPresetNumber(this, channel, bank, presetNumber)
40+
41+
override fun renderFloat(samples: Int, isMixing: Boolean): FloatArray = renderFloat(this, samples, isMixing)
3942
}

core/src/commonMain/kotlin/pl/lemanski/pandamidi/soundFont/internal/SoundFontExtInternal.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ internal expect fun bankNoteOff(delegate: SoundFontDelegate, bank: Int, presetNu
3030

3131
internal expect fun noteOffAll(delegate: SoundFontDelegate)
3232

33-
internal expect fun activeVoiceCount(delegate: SoundFontDelegate): Int
33+
internal expect fun activeVoiceCount(delegate: SoundFontDelegate): Int
34+
35+
internal expect fun setBankPresetNumber(delegate: SoundFontDelegate, channel: Int, bank: Int, presetNumber: Int)
36+
37+
internal expect fun renderFloat(delegate: SoundFontDelegate, samples: Int, isMixing: Boolean): FloatArray

core/src/mingwX64Main/kotlin/pl/lemanski/pandamidi/generator/Generator.mingwX64.kt

Lines changed: 28 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,21 @@
11
package pl.lemanski.pandamidi.generator
22

3-
import kotlinx.cinterop.CPointer
4-
import kotlinx.cinterop.ExperimentalForeignApi
5-
import kotlinx.cinterop.refTo
6-
import kotlinx.cinterop.reinterpret
7-
import kotlinx.cinterop.toByte
8-
import tinySoundFont.TSFOutputMode
9-
import tinySoundFont.tsf_channel_midi_control
10-
import tinySoundFont.tsf_channel_note_off
11-
import tinySoundFont.tsf_channel_note_on
12-
import tinySoundFont.tsf_channel_set_bank_preset
13-
import tinySoundFont.tsf_channel_set_pitchwheel
14-
import tinySoundFont.tsf_channel_set_presetnumber
15-
import tinySoundFont.tsf_load_filename
16-
import tinySoundFont.tsf_render_float
17-
import tinySoundFont.tsf_set_output
3+
import pl.lemanski.pandamidi.soundFont.SoundFont
4+
import pl.lemanski.pandamidi.soundFont.soundFont
185

196
internal object MinGWGenerator : Generator {
207
private const val SAMPLE_RATE = 44100
218
private const val CHANNEL_COUNT = 2
229
private const val SAMPLE_BLOCK_SIZE = 410
10+
private lateinit var soundFont: SoundFont
2311

24-
@OptIn(ExperimentalForeignApi::class)
25-
private var soundFont: CPointer<*>? = null
26-
27-
@OptIn(ExperimentalForeignApi::class)
2812
override fun setSoundFont(path: String) {
29-
soundFont = tsf_load_filename(path)
30-
tsf_channel_set_bank_preset(soundFont?.reinterpret(), 9, 128, 0)
31-
tsf_set_output(soundFont?.reinterpret(), TSFOutputMode.TSF_STEREO_INTERLEAVED, SAMPLE_RATE, 0.0f)
13+
soundFont = soundFont(path)
14+
soundFont.setBankPreset(9, 128, 0)
15+
soundFont.setOutput(SoundFont.OutputMode.TSF_STEREO_INTERLEAVED, SAMPLE_RATE, 0.0f)
3216
}
3317

34-
@OptIn(ExperimentalForeignApi::class)
3518
override fun generate(midiMessage: MidiMessage): FloatArray {
36-
if (soundFont == null) {
37-
throw IllegalStateException("SoundFont not set")
38-
}
39-
40-
val tmpBuffer = FloatArray(SAMPLE_BLOCK_SIZE * CHANNEL_COUNT)
41-
4219
var currentTime = 0.0
4320
var audioBuffer = FloatArray(0)
4421
var message: MidiMessage? = midiMessage
@@ -51,50 +28,36 @@ internal object MinGWGenerator : Generator {
5128

5229
while (targetTime >= currentTime) {
5330
currentTime += SAMPLE_BLOCK_SIZE * (1000.0 / SAMPLE_RATE)
54-
tsf_render_float(soundFont?.reinterpret(), tmpBuffer.refTo(0), SAMPLE_BLOCK_SIZE, 0)
55-
audioBuffer += tmpBuffer
31+
audioBuffer += soundFont.renderFloat(SAMPLE_BLOCK_SIZE, false)
5632
}
5733
}
5834

5935
return audioBuffer
6036
}
6137

62-
@OptIn(ExperimentalForeignApi::class)
6338
private fun MidiMessage.process() {
64-
if (soundFont == null) {
65-
// TODO add logging
66-
throw IllegalStateException("SoundFont not set")
67-
}
68-
6939
when (this) {
70-
is MidiMessageControlChange -> tsf_channel_midi_control(
71-
soundFont?.reinterpret(),
72-
channel.toInt(),
73-
control.toInt(),
74-
controlValue.toInt()
75-
)
76-
is MidiMessageNoteOff -> tsf_channel_note_off(
77-
soundFont?.reinterpret(),
78-
channel.toInt(),
79-
key.toInt()
80-
)
81-
is MidiMessageNoteOn -> tsf_channel_note_on(
82-
soundFont?.reinterpret(),
83-
channel.toInt(),
84-
key.toInt(),
85-
velocity.toFloat() / 127.0f
86-
)
87-
is MidiMessagePitchBend -> tsf_channel_set_pitchwheel(
88-
soundFont?.reinterpret(),
89-
channel.toInt(),
90-
pitchBend.toInt()
91-
)
92-
is MidiMessageProgramChange -> tsf_channel_set_presetnumber(
93-
soundFont?.reinterpret(),
94-
channel.toInt(),
95-
program.toInt(),
96-
(channel == 9u).toByte().toInt()
97-
)
40+
is MidiMessageNoteOff -> soundFont.noteOff(channel, key)
41+
is MidiMessageNoteOn -> soundFont.noteOn(channel, key, velocity / 127.0f)
42+
43+
// is MidiMessageControlChange -> tsf_channel_midi_control(
44+
// soundFont?.reinterpret(),
45+
// channel.toInt(),
46+
// control.toInt(),
47+
// controlValue.toInt()
48+
// )
49+
// is MidiMessagePitchBend -> tsf_channel_set_pitchwheel(
50+
// soundFont?.reinterpret(),
51+
// channel.toInt(),
52+
// pitchBend.toInt()
53+
// )
54+
// is MidiMessageProgramChange -> tsf_channel_set_presetnumber(
55+
// soundFont?.reinterpret(),
56+
// channel.toInt(),
57+
// program.toInt(),
58+
// (channel == 9u).toByte().toInt()
59+
// )
60+
else -> throw Exception("Unsupported command!")
9861
}
9962
}
10063
}

0 commit comments

Comments
 (0)