Making Noise With Kotlin/Native

74c04e4a4b803f12fa807966d73179ee?s=47 josh skeen
October 05, 2018
330

Making Noise With Kotlin/Native

Kotlin/Native lets you reach beyond the JVM, and into the world of hardware. In this talk we will use Kotlin/Native to create a hardware audio synthesizer. You will learn how to interface with C libraries and compile and deploy Kotlin to embedded systems. We will explore the state of the current Kotlin/Native ecosystem and what it provides. The result will be something we can jam with, Kotlin/Native-style!

http://github.com/mutexkid/kquarium

74c04e4a4b803f12fa807966d73179ee?s=128

josh skeen

October 05, 2018
Tweet

Transcript

  1. Making Noise with Kotlin/Native Josh Skeen | josh@bignerdranch.com | @mutexkid

    slides & code: https://speakerdeck.com/mutexkid/native
  2. Who am I? Android Developer & Instructor at Big Nerd

    Ranch Josh Skeen | josh@bignerdranch.com | @mutexkid
  3. Why Kotlin/Native?

  4. • Create native binaries for platforms like macOS or embedded

    • Write high performance audio code on the c layer • Interface with native binaries Why Kotlin/Native?
  5. • konanc - generates a native binary executable for a

    target platform (macOS, Linux variants, Windows, etc) Kotlin/Native Tools Overview
  6. Kotlin/Native Tools Overview • cinterop - generates interoperability bindings between

    a c library or program, and Kotlin code. • konanc - generates a native binary executable for a target platform (macOS, Linux variants, Windows, etc)
  7. A First Kotlin/Native Program #include <stdio.h> int main() { printf("hello

    c"); return 0; }
  8. Convert from C to Kotlin?

  9. A First Kotlin/Native Program

  10. A First Kotlin/Native Program

  11. Firing up Hopper a.out Hopper disassembler: https://www.hopperapp.com/

  12. konan runtime, kotlin/native stdlib, and more! Hopper disassembler: https://www.hopperapp.com/ program.kexe

    Firing up Hopper
  13. konan runtime, kotlin/native stdlib, and more! Hopper disassembler: https://www.hopperapp.com/ program.kexe

    Firing up Hopper
  14. konan runtime, kotlin/native stdlib, and more! Hopper disassembler: https://www.hopperapp.com/ program.kexe

    Firing up Hopper
  15. Firing up Hopper Hopper disassembler: https://www.hopperapp.com/ program.kexe

  16. Diving Deeper

  17. Building KQuarium • Configure the CLion IDE to work with

    Kotlin/Native • Call posix standard c library functions • Compile and interface with a c library (ncurses) via cinterop POSIX = portable operating system interface
  18. Kotlin Native Gradle Plugin plugins { id "org.jetbrains.kotlin.konan" version "0.9.2"

    } konanArtifacts { program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" } } plugins { id "org.jetbrains.kotlin.konan" version "0.9.2" } konanArtifacts { program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" } }
  19. Kotlin Native Gradle Plugin plugins { id "org.jetbrains.kotlin.konan" version "0.9.2"

    } konanArtifacts { program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" } } plugins { id "org.jetbrains.kotlin.konan" version "0.9.2" } konanArtifacts { program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" } }
  20. CLion: Gradle Plugin plugins { id "org.jetbrains.kotlin.konan" version "0.9.2" }

    konanArtifacts { program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" } } plugins { id "org.jetbrains.kotlin.konan" version "0.9.2" } konanArtifacts { program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" } }
  21. Kotlin Native Gradle Plugin

  22. CLion: Gradle Aware

  23. CLion: Gradle Aware

  24. CLion: Gradle Aware - External Libs

  25. // IntelliJ API Decompiler stub source generated from a class

    file // Implementation of methods is not available package kotlin.collections private const val INT_MAX_POWER_OF_TWO: kotlin.Int /* compiled code */ public val <T> kotlin.Array<out T>.indices: kotlin.ranges.IntRange /* compiled code */ public val kotlin.BooleanArray.indices: kotlin.ranges.IntRange /* compiled code */ public val kotlin.ByteArray.indices: kotlin.ranges.IntRange /* compiled code */ public val kotlin.CharArray.indices: kotlin.ranges.IntRange /* compiled code */ CLion: Gradle Aware - External Libs stdlib/linkdata/package_kotlin.collections.knm:
  26. KQuarium: Reading Fish Data /\ _/./ ,-' `-:..-'/ : o

    ) _ ( "`-....,--; `-.\ `' ,--.. .''-.,' /@ `.-: > )< ,-.: `..-',` `-' _______ ,-~~~ ~~~-, ( ) \_-, , , , , ,-_/ / / | | \ \ | | | | | | | | | | | | / / / \ \ \ | | | | | | art/fishone.txt art/fishtwo.txt art/fishthree.txt
  27. K/N Stdlib - Java.IO.File plz?

  28. K/N Stdlib - Java.IO.File plz? • Much more minimal than

    what you’re used to w/ Java Stdlib • Collections, Ranges, Math, Text, System
  29. Read Fish Data (hypothetical, c) #include <stdlib.h> char *readFile() {

    file = fopen(“fishone.txt”, "rb"); if (file) { while ((nread = fread(buf, 1, sizeof buf, file)) > 0) fwrite(buf, 1, nread, stdout); fclose(file); } } #include <stdlib.h> char *readFile() { file = fopen(“fishone.txt”, "rb"); if (file) { while ((nread = fread(buf, 1, sizeof buf, file)) > 0) fwrite(buf, 1, nread, stdout); fclose(file); } }
  30. Read Fish Data (hypothetical, c) #include <stdlib.h> char *readFile() {

    file = fopen(“fishone.txt”, "rb"); if (file) { while ((nread = fread(buf, 1, sizeof buf, file)) > 0) fwrite(buf, 1, nread, stdout); fclose(file); } } #include <stdlib.h> char *readFile() { file = fopen(“fishone.txt”, "rb"); if (file) { while ((nread = fread(buf, 1, sizeof buf, file)) > 0) fwrite(buf, 1, nread, stdout); fclose(file); } } #include <stdlib.h> char *readFile() { file = fopen(“fishone.txt”, "rb"); if (file) { while ((nread = fread(buf, 1, sizeof buf, file)) > 0) fwrite(buf, 1, nread, stdout); fclose(file); } } #include <stdlib.h> char *readFile() { file = fopen(“fishone.txt”, "rb"); if (file) { while ((nread = fread(buf, 1, sizeof buf, file)) > 0) fwrite(buf, 1, nread, stdout); fclose(file); } } #include <stdlib.h> char *readFile() { file = fopen(“fishone.txt”, "rb"); if (file) { while ((nread = fread(buf, 1, sizeof buf, file)) > 0) fwrite(buf, 1, nread, stdout); fclose(file); } }
  31. Kotlin/Native: Posix Library

  32. Kotlin/Native: Posix Library public fun fgets(arg0: kotlinx.cinterop.CValuesRef<kotlinx.cinterop.ByteVar /* = kotlinx.cinterop.ByteVarOf<kotlin.Byte>

    */>?, arg1: kotlin.Int, arg2: kotlinx.cinterop.CValuesRef<platform.posix.FILE /* = platform.posix.__sFILE */ >?): kotlinx.cinterop.CPointer<kotlinx.cinterop.ByteVar /* = kotlinx.cinterop.ByteVarOf<kotlin.Byte> */>? { /* compiled code */ }
  33. Read Fish Data with Posix & Stdlib ReadFile.kt import kotlinx.cinterop.*

    import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() }
  34. Read Fish Data with Posix & Stdlib ReadFile.kt import kotlinx.cinterop.*

    import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() }
  35. Read Fish Data with Posix & Stdlib ReadFile.kt import kotlinx.cinterop.*

    import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() }
  36. Read Fish Data with Posix & Stdlib ReadFile.kt import kotlinx.cinterop.*

    import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() } import kotlinx.cinterop.* import platform.posix.* fun readFileData(path: String): String { val pagesize = getpagesize() val filePointer = fopen(path, “r")!! val stringBuilder = StringBuilder() val charBuffer = nativeHeap.allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, this) != null) { stringBuilder.append(charArray.toKString()) } //free the allocated array (avoiding memory leak) free(charBuffer) return stringBuilder.toString() }
  37. Allocating a Byte Array: Cinterop ReadFile.kt import kotlinx.cinterop.* import platform.posix.*

    val charArray: CArrayPointer<ByteVar> = nativeHeap.allocArray<ByteVar>(pagesize) while (filePointer.readLine(charArray, pagesize) != null) { stringBuilder.append(charArray.toKString()) } free(charArray) import kotlinx.cinterop.* import platform.posix.* val charArray: CArrayPointer<ByteVar> = nativeHeap.allocArray<ByteVar>(pagesize) while (filePointer.readLine(charArray, pagesize) != null) { stringBuilder.append(charArray.toKString()) } free(charArray) import kotlinx.cinterop.* import platform.posix.* val charArray: CArrayPointer<ByteVar> = nativeHeap.allocArray<ByteVar>(pagesize) while (filePointer.readLine(charArray, pagesize) != null) { stringBuilder.append(charArray.toKString()) } free(charArray) import kotlinx.cinterop.* import platform.posix.* val charArray: CArrayPointer<ByteVar> = nativeHeap.allocArray<ByteVar>(pagesize) while (filePointer.readLine(charArray, pagesize) != null) { stringBuilder.append(charArray.toKString()) } free(charArray)
  38. Read Fish Data, Kotlin: memScoped ReadFile.kt fun readFileData(path: String) =

    memScoped { val charBuffer = allocArray<ByteVar>(pagesize) //do stuff with the allocated byte array while(fgets(charBuffer, pagesize, filePointer) != null) { stringBuilder.append(charArray.toKString()) } stringBuilder.toString() //memory freed at close of scope }
  39. Loading the Fish, Apply Kotlin Style fgets(filePointer, size, this) while

    (filePointer.readLine(charArray, pagesize, filePointer) != null) { sb.append(charArray.toKString()) } … fun CPointer<FILE>.readLine(charArray: CValuesRef<ByteArray>, size): = fgets(charArray, size, this) fun CPointer<FILE>.closeFile() = fclose(this) ReadFile.kt
  40. Compiling the Ncurses Library src/libs/ncurses-5.9 git:(master*) $ make install

  41. Compiling the Ncurses Library /usr/local/lib libncursesw.dylib /usr/local/include/ncursesw libncursesw.a ncurses.h /usr/local/lib

    libncursesw.dylib /usr/local/include/ncursesw libncursesw.a ncurses.h
  42. /usr/local/lib libncursesw.dylib /usr/local/include/ncursesw libncursesw.a ncurses.h Compiling the Ncurses Library /usr/local/lib

    libncursesw.dylib /usr/local/include/ncursesw libncursesw.a ncurses.h
  43. Ncurses Interop Bindings package = ncurses headers = ncursesw/ncurses.h compilerOpts=-I/usr/local/include/

    linkerOpts = -lncurses -L/usr/local/lib/ ncurses.def:
  44. Interop Bindings: Gradle Config konanArtifacts { interop('ncurses', targets: ['macbook']) {

    defFile 'ncurses.def' } program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" libraries { artifact 'ncurses' } } } konanArtifacts { interop('ncurses', targets: ['macbook']) { defFile 'ncurses.def' } program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" libraries { artifact 'ncurses' } } } konanArtifacts { interop('ncurses', targets: ['macbook']) { defFile 'ncurses.def' } program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" libraries { artifact 'ncurses' } } }
  45. Interop Bindings : Gradle Task

  46. Anatomy of a klib

  47. Anatomy of a klib: ncurses.kt … fun newwin(arg0: Int, arg1:

    Int, arg2: Int, arg3: Int): CPointer<WINDOW>? { return interpretCPointer<WINDOW>(kniBridge226(arg0, arg1, arg2, arg3)) } @SymbolName("ncurses_kniBridge226") private external fun kniBridge226(p0: Int, p1: Int, p2: Int, p3: Int): NativePtr …
  48. A Closer Look (Hopper Disassembler) Linked “dynamically” ; newwin ;

    _newwin_ptr, _newwin_ptr,_newwin ; DATA XREF=imp___stubs__newwin ; in /usr/lib/libncurses.5.4.dylib _ncurses_kniBridge226: 0000000100057fb0 call imp___stubs__newwin imp___stubs__newwin: // newwin 000000010005a992 jmp qword [_newwin_ptr] _newwin_ptr: 00000001000711e8 extern _newwin _newwin: 00000001000e8290 extern function code @SymbolName("ncurses_kniBridge226") private external fun kniBridge226(p0: Int, p1: Int, p2: Int, p3: Int): NativePtr
  49. package = ncurses headers = ncursesw/ncurses.h compilerOpts=-I/usr/local/include/ linkerOpts = -lncurses

    -L/usr/local/lib/ libraryPaths = /usr/local/lib staticLibraries = libncursesw.a ncurses.def Bundling a Static Library
  50. ncurses.klib Bundling a Static Library

  51. Using the Ncurses C Binding import ncurses.* fun main(args: Array<String>)

    { Game.run() } object Game { private var mainWindow: CPointer<WINDOW> = initscr()!! }
  52. Game Loop: Window main.kt object Game { private var mainWindow:

    CPointer<WINDOW> = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt() object Game { private var mainWindow: CPointer<WINDOW> = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt() fun run() { curs_set(0) while (true) { mainWindow.clear() for (fish in sealife) { mainWindow.refresh() fish.update() } mainWindow.refresh()
  53. Game Loop: Window main.kt object Game { private var mainWindow:

    CPointer<WINDOW> = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt() object Game { private var mainWindow: CPointer<WINDOW> = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt() fun run() { curs_set(0) while (true) { mainWindow.clear() for (fish in sealife) { mainWindow.refresh() fish.update() } mainWindow.refresh()
  54. Anatomy of a klib: ncurses.kt typealias WINDOW = _win_st class

    _win_st(rawPtr: NativePtr) : CStructVar(rawPtr) { ... var _maxy: Short get() = memberAt<ShortVar>(4).value set(value) { memberAt<ShortVar>(4).value = value } var _maxx: Short get() = memberAt<ShortVar>(6).value set(value) { memberAt<ShortVar>(6).value = value } ... }
  55. Game Loop: CPointer & Pointed main.kt private var mainWindow: CPointer<WINDOW>

    = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt() private var mainWindow: CPointer<WINDOW> = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt()
  56. Game Loop: CPointer & Pointed main.kt private var mainWindow: CPointer<WINDOW>

    = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt() private var mainWindow: CPointer<WINDOW> = initscr()!! val width: Int = mainWindow.pointed._maxx.toInt() val height: Int = mainWindow.pointed._maxy.toInt()
  57. Tidying up Ncurses w/ Extensions NcursesExt.kt import kotlinx.cinterop.CPointer import ncurses.*

    fun CPointer<WINDOW>.move(positionY: Int, positionX: Int) = mvwin(this, positionY, positionX) fun CPointer<WINDOW>.clear() = wclear(this) fun CPointer<WINDOW>.refresh() = wrefresh(this) fun CPointer<WINDOW>.print(data: String) = wprintw(this, data)
  58. Applying the Kotlin Style: Extensions NcursesExt.kt open class Fish(filePath: String,

    private val speed: Int = 3) { //fish-related behavior redacted for brevity fun update() { window.run { refresh() clear() move(posY, posX) print(frame) } posX += computeX posY += computeY } } open class Fish(filePath: String, private val speed: Int = 3) { //fish-related behavior redacted for brevity fun update() { window.run { refresh() clear() move(posY, posX) print(frame) } posX += computeX posY += computeY } }
  59. NcursesExt.kt Generating Sound • Write sound library in c, call

    from Kotlin/Native • Write the sound library in Kotlin/Native
  60. NcursesExt.kt Writing the CSynth lib • Write sound library in

    c, using c dependencies • Create a header • Write def file • Generate bindings • Call from kotlin/native
  61. csynth.c Writing the CSynth lib #include <math.h> #include <stdio.h> #include

    <stdlib.h> #include <string.h> #include <portaudio.h> typedef struct { int tableSize; int left_phase; int right_phase; float *sine; } synthData;
  62. csynth.c Writing the CSynth lib PaError playWave(int tableSize, long millis)

    { ... data.sine = malloc(sizeof(float) * tableSize); for (i = 0; i < tableSize; i++) { float d = (float) sin(((double) i / (double) tableSize) * M_PI * 3.0); data.sine[i] = d; } ... }
  63. csynth.c Writing the CSynth lib PaError playWave(int tableSize, long millis)

    { ... err = Pa_OpenStream( &stream, NULL, &outputParameters, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, audioCallback, &data); free(data.sine); ... }
  64. csynth.c Writing the CSynth lib PaError playWave(int tableSize, long millis)

    { ... err = Pa_OpenStream( &stream, NULL, &outputParameters, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, audioCallback, &data); free(data.sine); ... } PaError playWave(int tableSize, long millis) { ... err = Pa_OpenStream( &stream, NULL, &outputParameters, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, audioCallback, &data); free(data.sine); ... }
  65. csynth.c Writing the CSynth lib static int audioCallback(const void *inputBuffer,

    void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { synthData *data = (synthData *) userData; float *out = (float *) outputBuffer; unsigned long i; (void) timeInfo; (void) statusFlags; (void) inputBuffer; for (i = 0; i < framesPerBuffer; i++) { *out++ = data->sine[data->left_phase]; *out++ = data->sine[data->right_phase]; data->left_phase += 1; if (data->left_phase >= data->size) data->left_phase -= data->size; data->right_phase += 3; if (data->right_phase >= data->size) data->right_phase -= data->size; } return paContinue; }
  66. csynth.h CSynth lib Header #ifndef CSYNTH_H #define CSYNTH_H int playWave(int

    tableSize, int millis); #endif //CSYNTH_H
  67. csynth.def Kotlin Side - CSynth def file package = libcsynth

    headers = csynth.h compilerOpts=-I./libs/csynth/ linkerOpts = -lportaudio libraryPaths = ./libs/csynth/ staticLibraries = libcsynth.a
  68. csynth.def Kotlin Side - CSynth def file package = libcsynth

    headers = csynth.h compilerOpts=-I./libs/csynth/ linkerOpts = -lportaudio libraryPaths = ./libs/csynth/ staticLibraries = libcsynth.a
  69. csynth.def Kotlin Side - CSynth def file interop('libcsynth', targets: ['macbook'])

    { defFile ‘libcsynth.def' } program('kquarium', targets: ['macbook']) { srcDir "${project.rootDir}/src/main/kotlin" libraries { artifact 'ncurses' artifact 'libcsynth' } }
  70. Main.kt Kotlin Side - Calling CSynth sealife.forEach { fish ->

    sealife.forEach { if (it != fish && fish.hitTest(it)) { when (fish) { is Octopus -> libcsynth.play(it.posX * 15, 250) } } }
  71. Targeting Embedded • Raspberry Pi build target • “Cross-Compile” libraries

  72. Compiling for RPi: MacOS? ~/.konan/kotlin-native-macos-0.9.2/bin/konanc -list-targets macos_x64: (default) macbook, macos,

    imac ios_arm32: iphone32 ios_arm64: iphone, ipad, ios ios_x64: iphone_sim android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco: ~/.konan/kotlin-native-macos-0.9.2/bin/konanc -list-targets macos_x64: (default) macbook, macos, imac ios_arm32: iphone32 ios_arm64: iphone, ipad, ios ios_x64: iphone_sim android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco:
  73. Compiling for RPi: MacOS? ~/.konan/kotlin-native-macos-0.9.2/bin/konanc -list-targets macos_x64: (default) macbook, macos,

    imac ios_arm32: iphone32 ios_arm64: iphone, ipad, ios ios_x64: iphone_sim android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco: ~/.konan/kotlin-native-macos-0.9.2/bin/konanc -list-targets macos_x64: (default) macbook, macos, imac ios_arm32: iphone32 ios_arm64: iphone, ipad, ios ios_x64: iphone_sim android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco:
  74. Compiling for RPi: Docker https://github.com/JetBrains/kotlin-native/blob/master/tools/docker/Dockerfile docker run --rm -it -v

    $(pwd):/home/gradle -w /home/gradle joshskeen/kotlin-native root@9ed01dd64dcc:/home/gradle# ~/.konan/kotlin-native-linux-0.9.2/bin/konanc -list-targets linux_x64: (default) linux linux_arm32_hfp: raspberrypi linux_mips32: linux_mipsel32: android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco: docker run --rm -it -v $(pwd):/home/gradle -w /home/gradle joshskeen/kotlin-native root@9ed01dd64dcc:/home/gradle# ~/.konan/kotlin-native-linux-0.9.2/bin/konanc -list-targets linux_x64: (default) linux linux_arm32_hfp: raspberrypi linux_mips32: linux_mipsel32: android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco:
  75. Compiling for RPi: Docker https://github.com/JetBrains/kotlin-native/blob/master/tools/docker/Dockerfile docker run --rm -it -v

    $(pwd):/home/gradle -w /home/gradle joshskeen/kotlin-native root@9ed01dd64dcc:/home/gradle# ~/.konan/kotlin-native-linux-0.9.2/bin/konanc -list-targets linux_x64: (default) linux linux_arm32_hfp: raspberrypi linux_mips32: linux_mipsel32: android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco: docker run --rm -it -v $(pwd):/home/gradle -w /home/gradle joshskeen/kotlin-native root@9ed01dd64dcc:/home/gradle# ~/.konan/kotlin-native-linux-0.9.2/bin/konanc -list-targets linux_x64: (default) linux linux_arm32_hfp: raspberrypi linux_mips32: linux_mipsel32: android_arm32: android_arm64: wasm32: zephyr_stm32f4_disco:
  76. NcursesExt.kt Make Noise

  77. Why Kotlin/Native?

  78. Next: Terminal Tedium https://github.com/mxmxmx/terminal_tedium/

  79. Takeaways NcursesExt.kt • Kotlin Native opens the door to the

    C Layer with Interop • Minimal standard library • Supports multiple platforms, but C binary layer knows nothing about that!
  80. K/N Wishlist • Better cross-compilation support (macOS) • Incremental compilation

    • Include docs in the API code view (like Kotlin on JVM) • Built-in disassembler & pseudocode view • “convert c to kotlin/native”
  81. Resources • Hopper disassembler: https://www.hopperapp.com/ • Docker image: https://github.com/JetBrains/kotlin-native/blob/master/tools/ docker/Dockerfile

    • https://github.com/mutexkid/kquarium • https://github.com/mutexkid/kotlin-native-workshop • https://www.youtube.com/watch?v=443UNeGrFoM : ( How I program C, Eskil Steenberg )
  82. Thanks! Josh Skeen | josh@bignerdranch.com | @mutexkid https://speakerdeck.com/mutexkid/native